iot/dns: Add DNS Client support for Zephyr

This commit adds support the DNS client API on top of the new
native IP stack. Some features of this implementation are:

- Support for IPv4 and IPv6
- Support for multiple concurrent queries. A net_buf structure is
  required per context. See the DNS_RESOLVER_ADDITIONAL_BUF_CTR
  configuration variable

Origin: Original

Jira: ZEP-793
Jira: ZEP-855
Jira: ZEP-975

Change-Id: I351a636462a1b78a412c9bce1ef3cd0fa6223a52
Signed-off-by: Flavio Santes <flavio.santes@intel.com>
This commit is contained in:
Flavio Santes 2016-10-06 00:07:48 -05:00 committed by Jukka Rissanen
parent eb03404bce
commit 36bbd7a935
10 changed files with 1404 additions and 1 deletions

184
include/iot/dns_client.h Normal file
View file

@ -0,0 +1,184 @@
/*
* Copyright (c) 2016 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _DNS_CLIENT_H_
#define _DNS_CLIENT_H_
enum dns_query_type {
DNS_QUERY_TYPE_A = 1, /* IPv4 */
DNS_QUERY_TYPE_AAAA = 28 /* IPv6 */
};
#include <net/net_context.h>
#include <net/net_ip.h>
/**
* @brief dns_init DNS resolver initialization routine
* @details This routine must be called before any other
* dns routine.
* @return 0, always.
* Note: new versions may return error codes.
*/
int dns_init(void);
/**
* @brief dns4_resolve Retrieves the IPv4 addresses associated to the
* domain name 'name'.
* @details This routine obtains the IPv4 addresses
* associated to the domain name 'name'.
* The DNS server is specified by the sockaddr
* structure.
* Depending on the DNS server used, one or more
* IP addresses may be recovered by this routine.
* NOTE: You can use an IPv6 DNS server to look-up
* for IPv4 addresses or an IPv4 server to look-up
* for IPv6 address. Domain name services are not
* tied to any specific routing or transport
* technology.
* @param [in] ctx Previously initialized network context.
* @param [out] addresses An array of IPv4 addresses.
* @param [out] items Number of IPv4 addresses stored in 'addresses'.
* @param [in] elements Available positions in the addresses array.
* @param [in] name C-string containing the Domain Name to resolve,
* i.e. 'example.com'.
* @param [in] dns_server IP address and port number of the DNS server.
* @param [in] timeout RX/TX timeout, for example: TICKS_UNLIMITED.
* This timeout is also used when a buffer is
* required from the pool.
* @return 0 on success
*
* Number of returned addresses may be less than
* the one reported by the DNS server. So, it is
* considered a success because we are 'resolving'
* the 'name'.
* @return -EIO on network error.
* @return -EINVAL if an invalid parameter was passed as
* an argument to this routine. This value is also
* returned if the application received a malformed
* packet from the DNS server.
* @return -ENOMEM if there are no buffers available.
*/
int dns4_resolve(struct net_context *ctx, struct in_addr *addresses,
int *items, int elements, char *name,
struct sockaddr *dns_server, uint32_t timeout);
/**
* @brief dns6_resolve Retrieves the IPv6 addresses associated to the
* domain name 'name'.
* @details See 'details' at the 'dns4_resolve' routine.
* @param [in] ctx Previously initialized network context.
* @param [out] addresses An array of IPv6 addresses.
* @param [out] items Number of IPv6 addresses stored in 'addresses'.
* @param [in] elements Available positions in the addresses array.
* @param [in] name C-string containing the Domain Name to resolve,
* i.e. 'example.com'.
* @param [in] dns_server IP address and port number of the DNS server.
* @param [in] timeout RX/TX timeout, for example: TICKS_UNLIMITED.
* This timeout is also used when a buffer is
* required from the pool.
* @return 0 on success
*
* Number of returned addresses may be less than
* the one reported by the DNS server. So, it is
* considered a success because we are 'resolving'
* the 'name'.
* @return -EIO on network error
* @return -EINVAL if an invalid parameter was passed as
* an argument to this routine. This value is also
* returned if the application received a malformed
* packet from the DNS server.
* @return -ENOMEM if there are no buffers available
*/
int dns6_resolve(struct net_context *ctx, struct in6_addr *addresses,
int *items, int elements, char *name,
struct sockaddr *dns_server, uint32_t timeout);
/**
* @brief dns4_resolve_quick Retrives one IPv4 address associated to the
* domain name 'name'.
* @details See 'details' at the 'dns4_resolve' routine.
* @param [in] ctx Previously initialized network context.
* @param [out] address IPv4 address.
* @param [in] name C-string containing the Domain Name to resolve,
* i.e. 'example.com'.
* @param [in] dns_server IP address and port number of the DNS server.
* @param [in] timeout RX/TX timeout, for example: TICKS_UNLIMITED.
* This timeout is also used when a buffer is
* required from the pool.
* @return 0 on success
*
* Number of returned addresses may be less than
* the one reported by the DNS server. So, it is
* considered a success because we are 'resolving'
* the 'name'.
* @return -EIO on network error
* @return -EINVAL if an invalid parameter was passed as
* an argument to this routine. This value is also
* returned if the application received a malformed
* packet from the DNS server.
* @return -ENOMEM if there are no buffers available
*/
static inline
int dns4_resolve_quick(struct net_context *ctx, struct in_addr *address,
char *name, struct sockaddr *dns_server,
uint32_t timeout)
{
int elements = 1;
int items;
return dns4_resolve(ctx, address, &items, elements, name,
dns_server, timeout);
}
/**
* @brief dns6_resolve_quick Retrives one IPv6 address associated to the
* domain name 'name'.
* @details See 'details' at the 'dns4_resolve' routine.
* @param [in] ctx Previously initialized network context.
* @param [out] address IPv6 address.
* @param [in] name C-string containing the Domain Name to resolve,
* i.e. 'example.com'.
* @param [in] dns_server IP address and port number of the DNS server.
* @param [in] timeout RX/TX timeout, for example: TICKS_UNLIMITED.
* This timeout is also used when a buffer is
* required from the pool.
* @return 0 on success
*
* Number of returned addresses may be less than
* the one reported by the DNS server. So, it is
* considered a success because we are 'resolving'
* the 'name'.
* @return -EIO on network error
* @return -EINVAL if an invalid parameter was passed as
* an argument to this routine. This value is also
* returned if the application received a malformed
* packet from the DNS server.
* @return -ENOMEM if there are no buffers available
*/
static inline
int dns6_resolve_quick(struct net_context *ctx, struct in6_addr *address,
char *name, struct sockaddr *dns_server,
uint32_t timeout)
{
int elements = 1;
int items;
return dns6_resolve(ctx, address, &items, elements, name,
dns_server, timeout);
}
#endif

View file

@ -1 +1,2 @@
obj-$(CONFIG_ZOAP) += zoap/
obj-$(CONFIG_DNS_RESOLVER) += dns/

View file

@ -18,4 +18,6 @@ menu "IoT Protocols"
source "lib/iot/zoap/Kconfig"
source "lib/iot/dns/Kconfig"
endmenu

View file

@ -1,3 +1,7 @@
ifdef CONFIG_ZOAP
include $(srctree)/lib/iot/zoap/Makefile
endif
ifdef CONFIG_DNS_RESOLVER
include $(srctree)/lib/iot/dns/Makefile
endif

45
lib/iot/dns/Kconfig Normal file
View file

@ -0,0 +1,45 @@
#
# Copyright (c) 2016 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
config DNS_RESOLVER
bool
prompt "DNS resolver"
default n
help
This option enables the DNS client side support for Zephyr
config DNS_RESOLVER_ADDITIONAL_BUF_CTR
int
prompt "Additional DNS buffers"
depends on DNS_RESOLVER
default 0
help
Number of additional buffers available for the DNS resolver.
The DNS resolver requires at least one buffer. This option
enables additional buffers required for multiple concurrent
DNS connections.
config DNS_RESOLVER_ADDITIONAL_QUERIES
int
prompt "Additional DNS queries"
depends on DNS_RESOLVER
range 0 2
default 1
help
Number of additional DNS queries that the DNS resolver may
generate when the RR ANSWER only contains CNAME(s).
The maximum value of this variable is constrained to avoid
'alias loops'.

5
lib/iot/dns/Makefile Normal file
View file

@ -0,0 +1,5 @@
ccflags-y += -I$(srctree)/lib/iot/dns
obj-y := dns_pack.o
obj-y += dns_client.o

22
lib/iot/dns/README Normal file
View file

@ -0,0 +1,22 @@
DNS Client API for Zephyr
=========================
Known limitations:
- Synchronous queries
- Only IPv4 and IPv6 records can be handled
- Minimal protocol validation. If you do not trust your DNS server,
it is time to change it :)
Usage:
Before calling the resolver, it must be initialized via the 'dns_init'
routine.
'dnsX_resolve_quick' routines just return the first IP address.
However, domain names may be served by more IP addresses, so
'dnsX_resolve' routines may be more useful.
See samples/net/dns_client/src/main.c.

424
lib/iot/dns/dns_client.c Normal file
View file

@ -0,0 +1,424 @@
/*
* Copyright (c) 2016 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <iot/dns_client.h>
#include "dns_pack.h"
#include <drivers/rand32.h>
#include <net/buf.h>
#include <net/nbuf.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
/* RFC 1035, 3.1. Name space definitions
* To simplify implementations, the total length of a domain name (i.e.,
* label octets and label length octets) is restricted to 255 octets or
* less.
*/
#define DNS_MAX_NAME_LEN 255
#define DNS_QUERY_MAX_SIZE (DNS_MSG_HEADER_SIZE + DNS_MAX_NAME_LEN + \
DNS_QTYPE_LEN + DNS_QCLASS_LEN)
/* This value is recommended by RFC 1035 */
#define DNS_RESOLVER_MAX_BUF_SIZE 512
#define DNS_RESOLVER_MIN_BUF 1
#define DNS_RESOLVER_BUF_CTR (DNS_RESOLVER_MIN_BUF + \
CONFIG_DNS_RESOLVER_ADDITIONAL_BUF_CTR)
#define DNS_RESOLVER_QUERIES (1 + CONFIG_DNS_RESOLVER_ADDITIONAL_QUERIES)
/* Compressed RR uses a pointer to another RR. So, min size is 12 bytes without
* considering RR payload.
* See https://tools.ietf.org/html/rfc1035#section-4.1.4
*/
#define DNS_ANSWER_PTR_LEN 12
/* See dns_unpack_answer, and also see:
* https://tools.ietf.org/html/rfc1035#section-4.1.2
*/
#define DNS_QUERY_POS 0x0c
#define DNS_IPV4_LEN 4
#define DNS_IPV6_LEN 16
static struct nano_fifo dns_msg_fifo;
static NET_BUF_POOL(dns_msg_pool, DNS_RESOLVER_BUF_CTR,
DNS_RESOLVER_MAX_BUF_SIZE, &dns_msg_fifo, NULL, 0);
static struct nano_fifo dns_qname_fifo;
static NET_BUF_POOL(dns_qname_pool, DNS_RESOLVER_BUF_CTR,
DNS_MAX_NAME_LEN, &dns_qname_fifo, NULL, 0);
int dns_init(void)
{
net_buf_pool_init(dns_msg_pool);
net_buf_pool_init(dns_qname_pool);
return 0;
}
static
int dns_write(struct net_context *ctx, struct net_buf *dns_data,
uint32_t timeout, uint16_t dns_id, enum dns_query_type type,
struct net_buf *dns_qname, struct sockaddr *dns_server);
static
int dns_read(struct net_context *ctx, struct net_buf *dns_data,
uint32_t timeout, uint16_t dns_id, enum dns_query_type type,
uint8_t *addresses, int *items, int elements,
uint8_t *cname, uint16_t *cname_len);
/*
* Note about the DNS transaction identifier:
* The transaction identifier is randomized according to:
* http://www.cisco.com/c/en/us/about/security-center/dns-best-practices.html#3
* Here we assume that even after the cast, dns_id = sys_rand32_get(), there is
* enough entropy :)
*/
static
int dns_resolve(struct net_context *ctx, uint8_t *addresses, int *items,
int elements, char *name, enum dns_query_type type,
struct sockaddr *dns_server, uint32_t timeout)
{
struct net_buf *dns_data = NULL;
struct net_buf *dns_qname = NULL;
uint16_t dns_id;
int rc;
int i;
dns_id = sys_rand32_get();
dns_data = net_buf_get_timeout(&dns_msg_fifo, 0, timeout);
if (dns_data == NULL) {
rc = -ENOMEM;
goto exit_resolve;
}
dns_qname = net_buf_get_timeout(&dns_qname_fifo, 0, timeout);
if (dns_qname == NULL) {
rc = -ENOMEM;
goto exit_resolve;
}
rc = dns_msg_pack_qname(&dns_qname->len, dns_qname->data,
DNS_MAX_NAME_LEN, name);
if (rc != 0) {
rc = -EINVAL;
goto exit_resolve;
}
i = 0;
do {
rc = dns_write(ctx, dns_data, timeout, dns_id, type, dns_qname,
dns_server);
if (rc != 0) {
goto exit_resolve;
}
rc = dns_read(ctx, dns_data, timeout, dns_id, type,
addresses, items, elements, dns_qname->data,
&dns_qname->len);
if (rc != 0) {
goto exit_resolve;
}
/* Server response includes at least one IP address */
if (*items > 0) {
break;
}
} while (++i < DNS_RESOLVER_QUERIES);
rc = 0;
if (*items <= 0) {
rc = -EINVAL;
}
exit_resolve:
/* dns_data may be NULL, however net_nbuf_unref supports that */
net_nbuf_unref(dns_data);
net_nbuf_unref(dns_qname);
return rc;
}
int dns4_resolve(struct net_context *ctx, struct in_addr *addresses,
int *items, int elements, char *name,
struct sockaddr *dns_server, uint32_t timeout)
{
return dns_resolve(ctx, (uint8_t *)addresses, items, elements,
name, DNS_QUERY_TYPE_A, dns_server, timeout);
}
int dns6_resolve(struct net_context *ctx, struct in6_addr *addresses,
int *items, int elements, char *name,
struct sockaddr *dns_server, uint32_t timeout)
{
return dns_resolve(ctx, (uint8_t *)addresses, items, elements,
name, DNS_QUERY_TYPE_AAAA, dns_server, timeout);
}
static
int dns_write(struct net_context *ctx, struct net_buf *dns_data,
uint32_t timeout, uint16_t dns_id, enum dns_query_type type,
struct net_buf *dns_qname, struct sockaddr *dns_server)
{
struct net_buf *tx;
int server_addr_len;
int rc;
rc = dns_msg_pack_query(dns_data->data, &dns_data->len, dns_data->size,
dns_qname->data, dns_qname->len, dns_id,
(enum dns_rr_type)type);
if (rc != 0) {
rc = -EINVAL;
goto exit_write;
}
tx = net_nbuf_get_tx(ctx);
if (tx == NULL) {
rc = -ENOMEM;
goto exit_write;
}
rc = net_nbuf_write(tx, dns_data->len, dns_data->data);
if (rc != true) {
rc = -ENOMEM;
goto exit_write;
}
if (dns_server->family == AF_INET) {
server_addr_len = sizeof(struct sockaddr_in);
} else {
server_addr_len = sizeof(struct sockaddr_in6);
}
/* tx and dns_data buffers will be dereferenced after this call */
rc = net_context_sendto(tx, dns_server, server_addr_len, NULL,
timeout, NULL, NULL);
if (rc != 0) {
rc = -EIO;
goto exit_write;
}
rc = 0;
exit_write:
return rc;
}
static
void cb_recv(struct net_context *context, struct net_buf *buf, int status,
void *user_data)
{
struct net_buf **data;
ARG_UNUSED(context);
if (status != 0) {
return;
}
data = (struct net_buf **)user_data;
*data = buf;
}
static
int dns_recv(struct net_context *ctx, struct net_buf **buf, uint32_t timeout)
{
int rc;
rc = net_context_recv(ctx, cb_recv, timeout, buf);
if (rc != 0) {
return -EIO;
}
return 0;
}
static
int nbuf_copy(struct net_buf *dst, struct net_buf *src, int offset,
int len);
static
int dns_read(struct net_context *ctx, struct net_buf *dns_data,
uint32_t timeout, uint16_t dns_id, enum dns_query_type type,
uint8_t *addresses, int *items, int elements,
uint8_t *cname, uint16_t *cname_len)
{
struct net_buf *rx = NULL;
/* helper struct to track the dns msg received from the server */
struct dns_msg_t dns_msg;
/* RR ttl, so far it is not passed to caller */
uint32_t ttl;
uint8_t *src;
uint8_t *dst;
int address_size;
/* index that points to the current answer being analyzed */
int answer_ptr;
int data_len;
int offset;
int rc;
int i;
if (elements <= 0) {
rc = -EINVAL;
goto exit_error;
}
rc = dns_recv(ctx, &rx, timeout);
if (rc != 0) {
rc = -EIO;
goto exit_error;
}
data_len = min(net_nbuf_appdatalen(rx), DNS_RESOLVER_MAX_BUF_SIZE);
offset = net_buf_frags_len(rx) - data_len;
if (nbuf_copy(dns_data, rx, offset, data_len) != 0) {
rc = -ENOMEM;
goto exit_error;
}
dns_msg.msg = dns_data->data;
dns_msg.msg_size = data_len;
rc = dns_unpack_response_header(&dns_msg, dns_id);
if (rc != 0) {
rc = -EINVAL;
goto exit_error;
}
if (dns_header_qdcount(dns_msg.msg) != 1) {
rc = -EINVAL;
goto exit_error;
}
rc = dns_unpack_response_query(&dns_msg);
if (rc != 0) {
rc = -EINVAL;
goto exit_error;
}
if (type == DNS_QUERY_TYPE_A) {
address_size = DNS_IPV4_LEN;
} else {
address_size = DNS_IPV6_LEN;
}
/* while loop to traverse the response */
answer_ptr = DNS_QUERY_POS;
*items = 0;
i = 0;
while (i < dns_header_ancount(dns_msg.msg)) {
rc = dns_unpack_answer(&dns_msg, answer_ptr, &ttl);
if (rc != 0) {
rc = -EINVAL;
goto exit_error;
}
switch (dns_msg.response_type) {
case DNS_RESPONSE_IP:
if (dns_msg.response_length < address_size) {
/* it seems this is a malformed message */
rc = -EINVAL;
goto exit_error;
}
src = dns_msg.msg + dns_msg.response_position;
dst = (uint8_t *)addresses + *items * address_size;
memcpy(dst, src, address_size);
*items += 1;
if (*items >= elements) {
/* elements is always >= 1, so it is assumed
* that at least one address was returned.
*/
goto exit_ok;
}
break;
case DNS_RESPONSE_CNAME_NO_IP:
/* Instead of using the QNAME at DNS_QUERY_POS,
* we will use this CNAME
*/
answer_ptr = dns_msg.response_position;
break;
default:
rc = -EINVAL;
goto exit_error;
}
/* Update the answer offset to point to the next RR (answer) */
dns_msg.answer_offset += DNS_ANSWER_PTR_LEN;
dns_msg.answer_offset += dns_msg.response_length;
++i;
}
/* No IP addresses were found, so we take the last CNAME to generate
* another query. Number of additional queries is controlled via Kconfig
*/
if (*items == 0 && dns_msg.response_type == DNS_RESPONSE_CNAME_NO_IP) {
src = dns_msg.msg + dns_msg.response_position;
*cname_len = dns_msg.response_length;
memcpy(cname, src, *cname_len);
}
exit_ok:
rc = 0;
exit_error:
net_nbuf_unref(rx);
return rc;
}
static
int nbuf_copy(struct net_buf *dst, struct net_buf *src, int offset,
int len)
{
int copied;
int to_copy;
/* find the right fragment to start copying from */
while (src && offset >= src->len) {
offset -= src->len;
src = src->frags;
}
/* traverse the fragment chain until len bytes are copied */
copied = 0;
while (src && len > 0) {
to_copy = min(len, src->len - offset);
memcpy(dst->data + copied, src->data + offset, to_copy);
copied = to_copy;
len -= to_copy;
src = src->frags;
/* after the first iteration, this value will be 0 */
offset = 0;
}
if (len > 0) {
return -ENOMEM;
}
return 0;
}

352
lib/iot/dns/dns_pack.c Normal file
View file

@ -0,0 +1,352 @@
/*
* Copyright (c) 2016 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "dns_pack.h"
#include <string.h>
#define DNS_LABEL_MAX_SIZE 63
#define DNS_ANSWER_MIN_SIZE 12
#define DNS_COMMON_UINT_SIZE 2
#define DNS_HEADER_ID_LEN 2
#define DNS_HEADER_FLAGS_LEN 2
#define DNS_QTYPE_LEN 2
#define DNS_QCLASS_LEN 2
#define DNS_QDCOUNT_LEN 2
#define DNS_ANCOUNT_LEN 2
#define DNS_NSCOUNT_LEN 2
#define DNS_ARCOUNT_LEN 2
/* RFC 1035 '4.1.1. Header section format' defines the following flags:
* QR, Opcode, AA, TC, RD, RA, Z and RCODE.
* This implementation only uses RD (Recursion Desired).
*/
#define DNS_RECURSION 1
/* These two defines represent the 3rd and 4th bytes of the DNS msg header.
* See RFC 1035, 4.1.1. Header section format.
*/
#define DNS_FLAGS1 DNS_RECURSION /* QR, Opcode, AA, and TC = 0 */
#define DNS_FLAGS2 0 /* RA, Z and RCODE = 0 */
static inline
uint16_t dns_strlen(char *str)
{
if (str == NULL) {
return 0;
}
return (uint16_t)strlen(str);
}
int dns_msg_pack_qname(uint16_t *len, uint8_t *buf, uint16_t size,
char *domain_name)
{
uint16_t dn_size;
uint16_t lb_start;
uint16_t lb_index;
uint16_t lb_size;
uint16_t i;
lb_start = 0;
lb_index = 1;
lb_size = 0;
dn_size = dns_strlen(domain_name);
if (dn_size == 0) {
return -EINVAL;
}
/* traverse the domain name str, including the null-terminator :) */
for (i = 0; i < dn_size + 1; i++) {
if (lb_index >= size) {
return -ENOMEM;
}
switch (domain_name[i]) {
default:
buf[lb_index] = domain_name[i];
lb_size += 1;
break;
case '.':
buf[lb_start] = lb_size;
lb_size = 0;
lb_start = lb_index;
break;
case '\0':
buf[lb_start] = lb_size;
buf[lb_index] = 0;
break;
}
lb_index += 1;
}
*len = lb_index;
return 0;
}
static inline
void set_dns_msg_response(struct dns_msg_t *dns_msg, int type, uint16_t pos,
uint16_t len)
{
dns_msg->response_type = type;
dns_msg->response_position = pos;
dns_msg->response_length = len;
}
int dns_unpack_answer(struct dns_msg_t *dns_msg, int dname_ptr, uint32_t *ttl)
{
uint16_t buf_size;
uint16_t pos;
uint16_t len;
uint8_t *answer;
int ptr;
answer = dns_msg->msg + dns_msg->answer_offset;
if (answer[0] < DNS_LABEL_MAX_SIZE) {
return -ENOMEM;
}
/* Recovery of the pointer value */
ptr = (((answer[0] & DNS_LABEL_MAX_SIZE) << 8) + answer[1]);
if (ptr != dname_ptr) {
return -ENOMEM;
}
/*
* We need to be sure this buffer has enough space
* to contain the answer.
*
* size: dname_size + type + class + ttl + rdlength + rdata
* 2 + 2 + 2 + 4 + 2 + ?
*
* So, answer size >= 12
*
* See RFC-1035 4.1.3. Resource record format
*/
buf_size = dns_msg->msg_size - dns_msg->answer_offset;
if (buf_size < DNS_ANSWER_MIN_SIZE) {
return -ENOMEM;
}
/* Only DNS_CLASS_IN answers
* Here we use 2 as an offset because a ptr uses only 2 bytes.
*/
if (dns_answer_class(DNS_COMMON_UINT_SIZE, answer) != DNS_CLASS_IN) {
return -EINVAL;
}
/* TTL value */
*ttl = dns_answer_ttl(DNS_COMMON_UINT_SIZE, answer);
pos = dns_msg->answer_offset + DNS_ANSWER_MIN_SIZE;
len = dns_unpack_answer_rdlength(DNS_COMMON_UINT_SIZE, answer);
switch (dns_response_type(DNS_COMMON_UINT_SIZE, answer)) {
case DNS_RR_TYPE_A:
case DNS_RR_TYPE_AAAA:
set_dns_msg_response(dns_msg, DNS_RESPONSE_IP, pos, len);
return 0;
case DNS_RR_TYPE_CNAME:
set_dns_msg_response(dns_msg, DNS_RESPONSE_CNAME_NO_IP,
pos, len);
return 0;
default:
/* malformed dns answer */
return -EINVAL;
}
return 0;
}
int dns_unpack_response_header(struct dns_msg_t *msg, int src_id)
{
uint8_t *dns_header;
uint16_t size;
int qdcount;
int ancount;
int rc;
dns_header = msg->msg;
size = msg->msg_size;
if (size < DNS_MSG_HEADER_SIZE) {
return -ENOMEM;
}
if (dns_unpack_header_id(dns_header) != src_id) {
return -EINVAL;
}
if (dns_header_qr(dns_header) != DNS_RESPONSE) {
return -EINVAL;
}
if (dns_header_opcode(dns_header) != DNS_QUERY) {
return -EINVAL;
}
if (dns_header_z(dns_header) != 0) {
return -EINVAL;
}
rc = dns_header_rcode(dns_header);
switch (rc) {
case DNS_HEADER_NOERROR:
break;
default:
return rc;
}
qdcount = dns_unpack_header_qdcount(dns_header);
ancount = dns_unpack_header_ancount(dns_header);
if (qdcount < 1 || ancount < 1) {
return -EINVAL;
}
return 0;
}
static int dns_msg_pack_query_header(uint8_t *buf, uint16_t size, uint16_t id)
{
uint16_t offset;
if (size < DNS_MSG_HEADER_SIZE) {
return -ENOMEM;
}
UNALIGNED_PUT(htons(id), (uint16_t *)(buf));
/* RD = 1, TC = 0, AA = 0, Opcode = 0, QR = 0 <-> 0x01 (1B)
* RCode = 0, Z = 0, RA = 0 <-> 0x00 (1B)
*
* QDCOUNT = 1 <-> 0x0001 (2B)
*/
offset = DNS_HEADER_ID_LEN;
/* Split the following assignements just in case we need to alter
* the flags in future releases
*/
*(buf + offset) = DNS_FLAGS1; /* QR, Opcode, AA, TC and RD */
*(buf + offset + 1) = DNS_FLAGS2; /* RA, Z and RCODE */
offset += DNS_HEADER_FLAGS_LEN;
/* set question counter */
UNALIGNED_PUT(htons(1), (uint16_t *)(buf + offset));
offset += DNS_QDCOUNT_LEN;
/* set answer and ns rr */
UNALIGNED_PUT(0, (uint32_t *)(buf + offset));
offset += DNS_ANCOUNT_LEN + DNS_NSCOUNT_LEN;
/* set the additional records */
UNALIGNED_PUT(0, (uint16_t *)(buf + offset));
return 0;
}
int dns_msg_pack_query(uint8_t *buf, uint16_t *len, uint16_t size,
uint8_t *qname, uint16_t qname_len, uint16_t id,
enum dns_rr_type qtype)
{
uint16_t msg_size;
uint16_t offset;
int rc;
msg_size = DNS_MSG_HEADER_SIZE + DNS_QTYPE_LEN + DNS_QCLASS_LEN;
if (msg_size + qname_len > size) {
return -ENOMEM;
}
rc = dns_msg_pack_query_header(buf, size, id);
if (rc != 0) {
return rc;
}
offset = DNS_MSG_HEADER_SIZE;
memcpy(buf + offset, qname, qname_len);
offset += qname_len;
/* QType */
UNALIGNED_PUT(htons(qtype), (uint16_t *)(buf + offset + 0));
offset += DNS_QTYPE_LEN;
/* QClass */
UNALIGNED_PUT(htons(DNS_CLASS_IN), (uint16_t *)(buf + offset));
*len = offset + DNS_QCLASS_LEN;
return 0;
}
int dns_find_null(int *qname_size, uint8_t *buf, uint16_t size)
{
*qname_size = 0;
while (*qname_size < size) {
if (buf[(*qname_size)++] == 0x00) {
return 0;
}
}
return -ENOMEM;
}
int dns_unpack_response_query(struct dns_msg_t *dns_msg)
{
uint8_t *dns_query;
uint8_t *buf;
int remaining_size;
int qname_size;
int offset;
int rc;
dns_msg->query_offset = DNS_MSG_HEADER_SIZE;
dns_query = dns_msg->msg + dns_msg->query_offset;
remaining_size = dns_msg->msg_size - dns_msg->query_offset;
rc = dns_find_null(&qname_size, dns_query, remaining_size);
if (rc != 0) {
return rc;
}
/* header already parsed + qname size */
offset = dns_msg->query_offset + qname_size;
/* 4 bytes more due to qtype and qclass */
offset += DNS_QTYPE_LEN + DNS_QCLASS_LEN;
if (offset > dns_msg->msg_size) {
return -ENOMEM;
}
buf = dns_query + qname_size;
if (dns_unpack_query_qtype(buf) != DNS_RR_TYPE_A &&
dns_unpack_query_qtype(buf) != DNS_RR_TYPE_AAAA) {
return -EINVAL;
}
if (dns_unpack_query_qclass(buf) != DNS_CLASS_IN) {
return -EINVAL;
}
dns_msg->answer_offset = dns_msg->query_offset + qname_size +
DNS_QTYPE_LEN + DNS_QCLASS_LEN;
return 0;
}

364
lib/iot/dns/dns_pack.h Normal file
View file

@ -0,0 +1,364 @@
/*
* Copyright (c) 2016 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _DNS_PACK_H_
#define _DNS_PACK_H_
#include <net/net_ip.h>
#include <stdint.h>
#include <stddef.h>
#include <errno.h>
/* See RFC 1035, 4.1.1 Header section format
* DNS Message Header is always 12 bytes
*/
#define DNS_MSG_HEADER_SIZE 12
/**
* @brief dns_msg_t
*
* @details Structure that points to the buffer containing the
* DNS message. It also contains some decodified
* message's properties that can not be recovered easily:
*
* - cname_offset
*
* - query_offset
*
* - answer_offset:
*
* - response_type
* It indicates the response's content type. It could be
* an IP address, a CNAME with IP (two answers), a CNAME
* with no IP address. See enum dns_response_type for
* more details.
*
* - response_position: this is an offset. It holds the
* starting byte of the field containing the desired
* info. For example an IPv4 address.
*
* - response_length: this is an offset. It holds the
* response's length.
*/
struct dns_msg_t {
uint8_t *msg;
uint16_t msg_size;
int response_type;
uint16_t response_position;
uint16_t response_length;
uint16_t query_offset;
uint16_t answer_offset;
};
#define DNS_MSG_INIT(b, s) {.msg = b, .msg_size = s, \
.response_type = -EINVAL}
enum dns_rr_type {
DNS_RR_TYPE_INVALID = 0,
DNS_RR_TYPE_A = 1, /* IPv4 */
DNS_RR_TYPE_CNAME = 5, /* CNAME */
DNS_RR_TYPE_AAAA = 28 /* IPv6 */
};
enum dns_response_type {
DNS_RESPONSE_INVALID = -EINVAL,
DNS_RESPONSE_IP,
DNS_RESPONSE_CNAME_WITH_IP,
DNS_RESPONSE_CNAME_NO_IP
};
enum dns_class {
DNS_CLASS_INVALID = 0,
DNS_CLASS_IN,
};
enum dns_msg_type {
DNS_QUERY = 0,
DNS_RESPONSE
};
enum dns_header_rcode {
DNS_HEADER_NOERROR = 0,
DNS_HEADER_FORMATERROR,
DNS_HEADER_SERVERFAILURE,
DNS_HEADER_NAMEERROR,
DNS_HEADER_NOTIMPLEMENTED,
DNS_HEADER_REFUSED
};
/** It returns the ID field in the DNS msg header */
static inline int dns_header_id(uint8_t *header)
{
return htons(*(uint16_t *)header);
}
/* inline unpack routines are used to unpack data from network
* order to cpu. Similar routines without the unpack prefix are
* used for cpu to network order.
*/
static inline int dns_unpack_header_id(uint8_t *header)
{
return ntohs(*(uint16_t *)header);
}
/** It returns the QR field in the DNS msg header */
static inline int dns_header_qr(uint8_t *header)
{
return ((*(header + 2)) & 0x80) ? 1 : 0;
}
/** It returns the OPCODE field in the DNS msg header */
static inline int dns_header_opcode(uint8_t *header)
{
return ((*(header + 2)) & 0x70) >> 1;
}
/** It returns the AA field in the DNS msg header */
static inline int dns_header_aa(uint8_t *header)
{
return ((*(header + 2)) & 0x04) ? 1 : 0;
}
/** It returns the TC field in the DNS msg header */
static inline int dns_header_tc(uint8_t *header)
{
return ((*(header + 2)) & 0x02) ? 1 : 0;
}
/** It returns the RD field in the DNS msg header */
static inline int dns_header_rd(uint8_t *header)
{
return ((*(header + 2)) & 0x01) ? 1 : 0;
}
/** It returns the RA field in the DNS msg header */
static inline int dns_header_ra(uint8_t *header)
{
return ((*(header + 3)) & 0x80) >> 7;
}
/** It returns the Z field in the DNS msg header */
static inline int dns_header_z(uint8_t *header)
{
return ((*(header + 3)) & 0x70) >> 4;
}
/** It returns the RCODE field in the DNS msg header */
static inline int dns_header_rcode(uint8_t *header)
{
return ((*(header + 3)) & 0x0F);
}
/** It returns the QDCOUNT field in the DNS msg header */
static inline int dns_header_qdcount(uint8_t *header)
{
return htons(*(uint16_t *)(header + 4));
}
static inline int dns_unpack_header_qdcount(uint8_t *header)
{
return ntohs(*(uint16_t *)(header + 4));
}
/** It returns the ANCOUNT field in the DNS msg header */
static inline int dns_header_ancount(uint8_t *header)
{
return htons(*(uint16_t *)(header + 6));
}
static inline int dns_unpack_header_ancount(uint8_t *header)
{
return ntohs(*(uint16_t *)(header + 6));
}
/** It returns the NSCOUNT field in the DNS msg header */
static inline int dns_header_nscount(uint8_t *header)
{
return htons(*(uint16_t *)(header + 8));
}
/** It returns the ARCOUNT field in the DNS msg header */
static inline int dns_header_arcount(uint8_t *header)
{
return htons(*(uint16_t *)(header + 10));
}
static inline int dns_query_qtype(uint8_t *question)
{
return htons(*((uint16_t *)(question + 0)));
}
static inline int dns_unpack_query_qtype(uint8_t *question)
{
return ntohs(*((uint16_t *)(question + 0)));
}
static inline int dns_query_qclass(uint8_t *question)
{
return htons(*((uint16_t *)(question + 2)));
}
static inline int dns_unpack_query_qclass(uint8_t *question)
{
return ntohs(*((uint16_t *)(question + 2)));
}
static inline int dns_response_type(uint16_t dname_size, uint8_t *answer)
{
/** Future versions must consider byte 0
* 4.1.3. Resource record format
* *(answer + dname_size + 0);
*/
return *(answer + dname_size + 1);
}
static inline int dns_answer_class(uint16_t dname_size, uint8_t *answer)
{
/** Future versions must consider byte 2
* 4.1.3. Resource record format
* *(answer + dname_size + 2);
*/
return *(answer + dname_size + 3);
}
static inline int dns_answer_ttl(uint16_t dname_size, uint8_t *answer)
{
return htonl(*(uint32_t *)(answer + dname_size + 4));
}
static inline int dns_answer_rdlength(uint16_t dname_size, uint8_t *answer)
{
return htons(*(uint16_t *)(answer + dname_size + 8));
}
static inline
int dns_unpack_answer_rdlength(uint16_t dname_size, uint8_t *answer)
{
return ntohs(*(uint16_t *)(answer + dname_size + 8));
}
/**
* @brief dns_msg_pack_qname Packs a QNAME
* @param len Bytes used by this function
* @param buf Buffer
* @param sizeof Buffer's size
* @param domain_name Something like www.example.com
* @return 0 on success
* @return -ENOMEM if there is no enough space to store
* the resultant QNAME
* @return -EINVAL if an invalid parameter was passed as
* an argument
*/
int dns_msg_pack_qname(uint16_t *len, uint8_t *buf, uint16_t size,
char *domain_name);
/**
* @brief dns_unpack_answer Unpacks an answer message
* @param dns_msg Structure
* @param dname_ptr An index to the previous CNAME. For example
* for the first answer, ptr must be 0x0c, the
* DNAME at the question.
* @param ttl TTL answer parameter.
* @return 0 on success
* @return -ENOMEM on error
*/
int dns_unpack_answer(struct dns_msg_t *dns_msg, int dname_ptr, uint32_t *ttl);
/**
* @brief dns_unpack_response_header
*
* @details Unpacks the header's response.
*
* @param msg Structure containing the response.
*
* @param src_id Transaction id, it must match the id
* used in the query datagram sent to the
* DNS server.
* @return 0 on success
*
* @return -ENOMEM if the buffer in msg has no
* enough space to store the header.
* The header is always 12 bytes length.
*
* @return -EINVAL:
* * if the src_id does not match the
* header's id.
* * if the header's QR value is
* not DNS_RESPONSE.
* * if the header's OPCODE value is not
* DNS_QUERY.
* * if the header's Z value is not 0.
* * if the question counter is not 1 or
* the answer counter is less than 1.
*
* RFC 1035 RCODEs (> 0):
*
* 1 Format error
* 2 Server failure
* 3 Name Error
* 4 Not Implemented
* 5 Refused
*
*/
int dns_unpack_response_header(struct dns_msg_t *msg, int src_id);
/**
* @brief dns_msg_pack_query Packs the query message
* @param [out] buf Buffer that will contain the resultant query
* @param [out] len Number of bytes used to encode the query
* @param [in] size Buffer size
* @param [in] qname Domain name represented as a sequence of labels.
* See RFC 1035, 4.1.2. Question section format.
* @param [in] qname_len Number of octects in qname.
* @param [in] id Transaction Identifier
* @param [in] qtype Query type: AA, AAAA. See enum dns_rr_type
* @return 0 on success
* @return On error, a negative value is returned. See:
* - dns_msg_pack_query_header
* - dns_msg_pack_qname
*/
int dns_msg_pack_query(uint8_t *buf, uint16_t *len, uint16_t size,
uint8_t *qname, uint16_t qname_len, uint16_t id,
enum dns_rr_type qtype);
/**
* @brief dns_unpack_response_query
*
* @details Unpacks the response's query. RFC 1035 states that the
* response's query comes after the first 12 bytes,
* i.e. afther the message's header.
*
* This function computes the answer_offset field.
*
* @param dns_msg Structure containing the message.
*
* @return 0 on success
* @return -ENOMEM:
* * if the null label is not found after
* traversing the buffer.
* * if QCLASS and QTYPE are not found.
* @return -EINVAL:
* * if QTYPE is not "A" (IPv4) or "AAAA" (IPv6).
* * if QCLASS is not "IN".
*
*/
int dns_unpack_response_query(struct dns_msg_t *dns_msg);
#endif