1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

Merge pull request #16705 from miri64/gcoap_dns/feat/initial

gcoap_dns: initial import of a DNS over CoAP (DoC) client
This commit is contained in:
Martine Lenders 2022-07-13 02:27:17 +02:00 committed by GitHub
commit a8254d52b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1923 additions and 0 deletions

View File

@ -67,6 +67,10 @@ PSEUDOMODULES += fmt_%
PSEUDOMODULES += gcoap_forward_proxy
PSEUDOMODULES += gcoap_fileserver
PSEUDOMODULES += gcoap_dtls
## Enable @ref net_gcoap_dns
PSEUDOMODULES += gcoap_dns
## Enable the @ref gcoap_dns_server_proxy_set function
PSEUDOMODULES += gcoap_dns_proxied
PSEUDOMODULES += fido2_tests
PSEUDOMODULES += gnrc_dhcpv6_%
PSEUDOMODULES += gnrc_ipv6_auto_subnets_auto_init

View File

@ -676,6 +676,15 @@ ifneq (,$(filter gcoap,$(USEMODULE)))
USEMODULE += random
endif
ifneq (,$(filter gcoap_dns,$(USEMODULE)))
USEMODULE += dns_msg
USEMODULE += gcoap
USEMODULE += ipv4_addr
USEMODULE += ipv6_addr
USEMODULE += uri_parser
USEMODULE += sock_util
endif
ifneq (,$(filter luid,$(USEMODULE)))
FEATURES_OPTIONAL += periph_cpuid
endif

View File

@ -154,6 +154,7 @@ extern "C" {
#define COAP_FORMAT_SENSML_EXI (115)
#define COAP_FORMAT_SENML_XML (310)
#define COAP_FORMAT_SENSML_XML (311)
#define COAP_FORMAT_DNS_MESSAGE (65053) /**< NON STANDARD! */
/** @} */
/**

219
sys/include/net/gcoap/dns.h Normal file
View File

@ -0,0 +1,219 @@
/*
* Copyright (C) 2021 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup net_gcoap_dns DNS over CoAP client implementation
* @ingroup net_gcoap
*
* @brief A DNS over CoAP client prototype based on gCoAP.
*
* DNS over CoAP allows a node to use a CoAP server to resolve DNS request, following
* [draft-lenders-dns-over-coap](https://datatracker.ietf.org/doc/draft-lenders-dns-over-coap/).
*
* The `gcoap_dns` module does not replace the @ref sock_dns_query function when built, and is not
* used as a back-end to @ref netutils_get_ipv6 automatically. It does, however, provide a drop-in
* replacement for @ref sock_dns_query in its @ref gcoap_dns_query function.
*
* @{
*
* @file
* @brief `gcoap_dns` definitions
*
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#ifndef NET_GCOAP_DNS_H
#define NET_GCOAP_DNS_H
#include <stdint.h>
#include "mutex.h"
#include "net/credman.h"
#include "net/coap.h"
#include "net/gcoap.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @defgroup gcoap_dns_config GCoAP DNS over CoAP client configuration
* @ingroup config
* @{
*/
/**
* @brief maximum length of the URI for the DNS server
*/
#ifndef CONFIG_GCOAP_DNS_SERVER_URI_LEN
#define CONFIG_GCOAP_DNS_SERVER_URI_LEN 64U
#endif
/**
* @brief maximum number of credentials that can be added with @ref gcoap_dns_cred_add()
*/
#ifndef CONFIG_GCOAP_DNS_CREDS_MAX
#if IS_USED(MODULE_GCOAP_DTLS) || defined(DOXYGEN)
#define CONFIG_GCOAP_DNS_CREDS_MAX 1U
#else
#define CONFIG_GCOAP_DNS_CREDS_MAX 0U
#endif
#endif
/**
* @brief size of the buffer used to build a CoAP request
*/
#ifndef CONFIG_GCOAP_DNS_PDU_BUF_SIZE
#define CONFIG_GCOAP_DNS_PDU_BUF_SIZE 128U
#endif
/**
* @brief block-size used for requests (both Block2 control and Block1)
*
* Should be less than @ref CONFIG_GCOAP_DNS_PDU_BUF_SIZE or
* @ref CONFIG_GCOAP_PDU_BUF_SIZE and must be a power
* of 2
*/
#ifndef CONFIG_GCOAP_DNS_BLOCK_SIZE
#define CONFIG_GCOAP_DNS_BLOCK_SIZE 64U
#endif
/** @} */
/**
* @brief Query a domain name via CoAP synchronously
*
* @param[in] domain_name A '\0'-terminated domain name. Must not be NULL.
* @param[out] addr_out The resolved address. Must not be NULL.
* @param[in] family The desired address family for @p addr_out.
* @ref AF_UNSPEC for any address family (an IPv6
* address will take preference over an IPv4 address).
*
* @return length of @p addr_out in bytes on success
* @return -EAFNOSUPPORT, if the hostname of the URI resolves to an unknown address family.
* @return -EBADMSG, when receiving erroneous response or response containing
* an error code.
* @return -ECONNABORTED, if CoAP request cannot be sent.
* @return -ECONNREFUSED, if no URI is set for the client (see @ref gcoap_dns_server_uri_set()).
* @return -EDESTADDRREQ, if CoAP response was received from an unexpected
* remote.
* @return -EHOSTUNREACH, if the hostname of the URI can not be resolved
* @return -ENOBUFS, if there was not enough buffer space for the request.
* @return -ENOBUFS, if length of received CoAP body is greater than
* @ref CONFIG_DNS_MSG_LEN.
* @return -ENOENT, if Zone-ID of the URI can not be found locally.
* @return -ENOMSG, if CoAP response did not contain a DNS response.
* @return -ENOTRECOVERABLE, on gCoAP-internal error.
* @return -ENOTSUP, if credential can not be added for to client.
* @return -ETIMEDOUT, if CoAP request timed out.
*/
int gcoap_dns_query(const char *domain_name, void *addr_out, int family);
/**
* @brief Sets and checks a URI for a DoC server
*
* @param[in] uri A URI. May be NULL to remove the URI.
Unless the @ref net_sock_dns module is also used, the host
component of the URI needs to be an IP literal.
*
* @return length of @p uri on success (0 when URI is removed).
* @return -EINVAL, if @p uri is not a valid URI for DNS over CoAP.
* @return -ENOBUFS, if the client implementation has not enough buffer space
* to process the URI.
*/
int gcoap_dns_server_uri_set(const char *uri);
/**
* @brief Checks if the URI for the DoC server is set.
*
* @retval true A URI for the DoC server is set.
* @retval false There is no URI set for the DoC server.
*/
bool gcoap_dns_server_uri_is_set(void);
/**
* @brief Gets the URI for the DoC server
*
* @param[out] uri The current URI for the DoC server
* @param[in] uri_len Maximum length for @p uri
*
* @return Length of the @p uri on return. 0, if no URI is set.
* @return -ENOBUFS, if the configured URI is longer than @p uri_len.
*/
ssize_t gcoap_dns_server_uri_get(char *uri, size_t uri_len);
/**
* @brief Deletes all added credentials
*
* This also removes the credentials from the @ref net_credman and the GCoAP DTLS sock.
*/
void gcoap_dns_cred_reset(void);
/**
* @brief Adds a credential for the use with the configured DoC server.
*
* @pre creds != NULL.
*
* @param[in] creds A crential. May not be NULL.
*
* @return 0 on success
* @return -ENOTSUP if DTLS is not supported (because `gcoap_dtls` is not compiled in)
* @return -ENOMEM if @ref CONFIG_GCOAP_DNS_CREDS_MAX is exceeded (duplicates may be added).
* @return -EBADF if the credential can not be added to @ref net_credman or the GCoAP DTLS sock.
*/
int gcoap_dns_cred_add(credman_credential_t *creds);
/**
* @brief Remove a credential for the use with the configured DoC server
*
* This also removes the credentials from the @ref net_credman and the GCoAP DTLS sock.
*
* @param[in] tag The tag of the credential.
* @param[in] type The type of the credential.
*/
void gcoap_dns_cred_remove(credman_tag_t tag, credman_type_t type);
/**
* @brief Deletes the proxy URI.
*/
void gcoap_dns_server_proxy_reset(void);
/**
* @brief Sets and checks a proxy URI.
*
* @param[in] proxy A proxy URI. Must contain a `dns` query variable.
*
* @return length of @p proxy on success.
* @return -ENOBUFS, if the client implementation has not enough buffer space
* to process the proxy URI.
* @return -ENOSUP, if module `gcoap_dns_proxied` is not compiled in.
*/
int gcoap_dns_server_proxy_set(const char *proxy);
/**
* @brief Checks if a proxy URI is set.
*
* @retval true A proxy URI is set.
* @retval false There is no URI set for the DoC server.
*/
bool gcoap_dns_server_proxy_is_set(void);
/**
* @brief Gets the proxy URI
*
* @param[out] proxy The current proxy URI
* @param[in] proxy_len Maximum length for @p proxy
*
* @return Length of the @p proxy on return. 0, if no URI is set.
* @return -ENOBUFS, if the configured URI is longer than @p proxy_len.
*/
ssize_t gcoap_dns_server_proxy_get(char *proxy, size_t proxy_len);
#ifdef __cplusplus
}
#endif
#endif /* NET_GCOAP_DNS_H */
/** @} */

View File

@ -13,6 +13,37 @@ menuconfig KCONFIG_USEMODULE_GCOAP
if KCONFIG_USEMODULE_GCOAP
menuconfig KCONFIG_USEMODULE_GCOAP_DNS
bool "Configure DNS-over-CoAPS implementation in GCoAP"
depends on USEMODULE_GCOAP_DNS
help
Configure DNS-over-CoAPS submodule of Gcoap using Kconfig. If not set
default values and CFLAGS will be used.
if KCONFIG_USEMODULE_GCOAP_DNS
config GCOAP_DNS_SERVER_URI_LEN
int "Maximum length of the URI template for the DNS server"
default 64
range 9 65535 # 14 == len("coaps:///")
config GCOAP_DNS_CREDS_MAX
int "Maximum number of credentials that can be added with @ref gcoap_dns_cred_add()"
default 1 if USEMODULE_GCOAP_DTLS
default 0
config GCOAP_DNS_PDU_BUF_SIZE
int "Size of the buffer used to build a CoAP request"
default 128
config GCOAP_DNS_BLOCK_SIZE
int "Block-size used for requests (both Block2 control and Block1)"
default 64
help
Should be less than @ref CONFIG_GCOAP_DNS_PDU_BUF_SIZE or
@ref CONFIG_GCOAP_PDU_BUF_SIZE and must be a power
endif # KCONFIG_USEMODULE_GCOAP_DNS
menu "DTLS options"
config GCOAP_DTLS_CREDENTIAL_TAG
int "Credential tag"

View File

@ -0,0 +1,737 @@
/*
* Copyright (C) 2021 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @{
*
* @file
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include "fmt.h"
#include "log.h"
#include "mutex.h"
#include "net/credman.h"
#include "net/gcoap.h"
#include "net/af.h"
#include "net/ipv4/addr.h"
#include "net/ipv6/addr.h"
#include "net/sock/dns.h"
#include "net/sock/udp.h"
#include "net/sock/util.h"
#include "random.h"
#include "uri_parser.h"
#include "ut_process.h"
#include "net/gcoap/dns.h"
#define ENABLE_DEBUG 0
#include "debug.h"
/**
* @brief Context for a DNS query-response-pair.
*/
typedef struct {
/**
* @brief Synchronization mutex to wait for response
*/
mutex_t resp_wait;
/**
* @brief The CoAP request packet
*
* Only needs to have coap_pkt_t::payload and coap_pkt_t::payload_len
* initialized.
*/
coap_pkt_t *pkt;
void *dns_buf; /**< The buffer for the DNS message exchange */
void *addr_out; /**< Pointer to the resulting address */
/**
* @brief Status for the DNS message exchange
*
* - length of _req_ctx_t::addr_out in bytes on success
* - -EBADMSG, when receiving erroneous response or response containing
* - -EDESTADDRREQ, if CoAP response was received from an unexpected remote.
* - -EINVAL, when block-wise transfer can not be completed.
* - -ENOBUFS, if length of received CoAP body is greater than
* @ref CONFIG_DNS_MSG_LEN.
* - -ENOMSG, if CoAP response did not contain a DNS response.
* - -ETIMEDOUT, if CoAP request timed out.
*/
int res;
uint8_t dns_buf_len; /**< Length of _req_ctx_t::dns_buf */
int8_t family; /**< Address family to resolve */
/**
* @brief The current block number for block-wise transfer
*
* Leave unset on function call.
*/
uint8_t cur_blk_num;
#if IS_USED(MODULE_GCOAP_DTLS) || defined(DOXYGEN)
/**
* @brief Request tag to rule out potential request reordering attacks
*/
uint16_t req_tag;
#endif
} _req_ctx_t;
typedef struct {
credman_type_t type; /**< Type of the credential */
credman_tag_t tag; /**< Tag of the credential */
} _cred_t;
static mutex_t _client_mutex = MUTEX_INIT;
static char _uri[CONFIG_GCOAP_DNS_SERVER_URI_LEN];
static char _proxy[CONFIG_GCOAP_DNS_SERVER_URI_LEN];
static uri_parser_result_t _uri_comp;
static sock_udp_ep_t _remote;
#if IS_USED(MODULE_GCOAP_DTLS)
static _cred_t _creds[CONFIG_GCOAP_DNS_CREDS_MAX] = { 0 };
#endif
static uint16_t _req_tag;
static inline bool _dns_server_uri_isset(void);
#if IS_USED(MODULE_GCOAP_DTLS)
static void _remove_cred(sock_dtls_t *sock, _cred_t *cred);
#else
#define _remove_cred(sock, cred) (void)(sock); (void)(cred)
#endif
static inline bool _is_proxied(void);
static int _add_init_block2_opt(coap_pkt_t *pdu);
static int _add_remaining_options(coap_pkt_t *pdu, const char *proxy_uri, _req_ctx_t *context);
static int _dns_query(const char *domain_name, _req_ctx_t *req_ctx);
static ssize_t _send(const void *buf, size_t len, const sock_udp_ep_t *remote,
bool lock_resp_wait, _req_ctx_t *context, gcoap_socket_type_t tl_type);
int gcoap_dns_query(const char *domain_name, void *addr_out, int family)
{
static uint8_t coap_buf[CONFIG_GCOAP_DNS_PDU_BUF_SIZE];
static uint8_t dns_buf[CONFIG_DNS_MSG_LEN];
int res;
coap_pkt_t pdu;
_req_ctx_t req_ctx = {
.resp_wait = MUTEX_INIT,
.pkt = &pdu,
.dns_buf = dns_buf,
.addr_out = addr_out,
.family = family,
};
mutex_lock(&_client_mutex);
pdu.payload = coap_buf;
pdu.payload_len = sizeof(coap_buf);
res = _dns_query(domain_name, &req_ctx);
if (res > 0) {
/* wait for req_ctx.addr_out to be set */
mutex_lock(&req_ctx.resp_wait);
res = req_ctx.res;
}
mutex_unlock(&_client_mutex);
return res;
}
int gcoap_dns_server_uri_set(const char *uri)
{
int res;
if (!uri) {
mutex_lock(&_client_mutex);
_uri[0] = '\0';
mutex_unlock(&_client_mutex);
return 0;
}
if (IS_USED(MODULE_GCOAP_DTLS)) {
/* reinitialize request tag */
_req_tag = (uint16_t)random_uint32();
}
if ((strncmp(uri, "coap:", sizeof("coap:") - 1) != 0) &&
/* if gcoap_dtls is used, URIs not starting with coaps: are also invalid */
(!IS_USED(MODULE_GCOAP_DTLS) || (strncmp(uri, "coaps:", sizeof("coaps:") - 1) != 0))) {
return -EINVAL;
}
res = strlen(uri);
if ((((unsigned)res + 1) >= CONFIG_GCOAP_DNS_SERVER_URI_LEN)) {
return -ENOBUFS;
}
mutex_lock(&_client_mutex);
strcpy(_uri, uri);
if (uri_parser_process(&_uri_comp, _uri, res) < 0) {
res = -EINVAL;
_uri[0] = '\0';
}
mutex_unlock(&_client_mutex);
return res;
}
bool gcoap_dns_server_uri_is_set(void)
{
mutex_lock(&_client_mutex);
bool res = _dns_server_uri_isset();
mutex_unlock(&_client_mutex);
return res;
}
ssize_t gcoap_dns_server_uri_get(char *uri, size_t uri_len)
{
ssize_t res = 0;
mutex_lock(&_client_mutex);
if (_dns_server_uri_isset()) {
res = strlen(_uri);
if ((size_t)(res + 1) > uri_len) {
/* account for trailing \0 */
res = -ENOBUFS;
}
else {
strcpy(uri, _uri);
}
}
mutex_unlock(&_client_mutex);
return res;
}
void gcoap_dns_cred_reset(void)
{
#if IS_USED(MODULE_GCOAP_DTLS)
sock_dtls_t *sock = gcoap_get_sock_dtls();
for (unsigned i = 0; i < ARRAY_SIZE(_creds); i++) {
if (_creds[i].type != CREDMAN_TYPE_EMPTY) {
_remove_cred(sock, &_creds[i]);
}
}
#endif
}
int gcoap_dns_cred_add(credman_credential_t *creds)
{
_cred_t *c = NULL;
if (!IS_USED(MODULE_GCOAP_DTLS)) {
return -ENOTSUP;
}
assert(creds != NULL);
#if IS_USED(MODULE_GCOAP_DTLS)
for (unsigned i = 0; i < ARRAY_SIZE(_creds); i++) {
if (!c &&
(((_creds[i].type == creds->type) && (_creds[i].tag == creds->tag)) ||
(_creds[i].type == CREDMAN_TYPE_EMPTY))) {
c = &_creds[i];
break;
}
}
#endif
if (c == NULL) {
DEBUG("gcoap_dns: no space to store credential tag\n");
return -ENOMEM;
}
int res = credman_add(creds);
if ((res < 0) && (res != CREDMAN_EXIST)) {
/* ignore duplicate credentials */
DEBUG("gcoap_dns: cannot add credential to system: %d\n", res);
return -EBADF;
}
if (res == CREDMAN_OK) {
#if IS_USED(MODULE_GCOAP_DTLS)
/* functions used in here are only available with module gcoap_dtls, so guard this
* section */
sock_dtls_t *gcoap_sock_dtls = gcoap_get_sock_dtls();
res = sock_dtls_add_credential(gcoap_sock_dtls, creds->tag);
if (res < 0) {
DEBUG("gcoap_dns: cannot add credential to GCoAPs DTLS sock\n");
return -EBADF;
}
#endif
c->tag = creds->tag;
c->type = creds->type;
}
return 0;
}
void gcoap_dns_cred_remove(credman_tag_t tag, credman_type_t type)
{
#if IS_USED(MODULE_GCOAP_DTLS)
sock_dtls_t *sock = gcoap_get_sock_dtls();
for (unsigned i = 0; i < ARRAY_SIZE(_creds); i++) {
if ((_creds[i].tag == tag) && (_creds[i].type == type)) {
_remove_cred(sock, &_creds[i]);
break;
}
}
#else
(void)tag;
(void)type;
#endif
}
void gcoap_dns_server_proxy_reset(void)
{
if (IS_USED(MODULE_GCOAP_DNS_PROXIED)) {
mutex_lock(&_client_mutex);
_proxy[0] = '\0';
mutex_unlock(&_client_mutex);
}
}
int gcoap_dns_server_proxy_set(const char *proxy)
{
int res;
if (!IS_USED(MODULE_GCOAP_DNS_PROXIED)) {
return -ENOTSUP;
}
res = strlen(proxy);
if ((unsigned)(res + 1) >= CONFIG_GCOAP_DNS_SERVER_URI_LEN) {
return -EINVAL;
}
mutex_lock(&_client_mutex);
strcpy(_proxy, proxy);
mutex_unlock(&_client_mutex);
return res;
}
ssize_t gcoap_dns_server_proxy_get(char *proxy, size_t proxy_len)
{
ssize_t res = 0;
mutex_lock(&_client_mutex);
if (_dns_server_uri_isset()) {
res = strlen(_uri);
if (((size_t)res + 1) > proxy_len) {
/* account for trailing \0 */
res = -ENOBUFS;
}
else {
strcpy(proxy, _proxy);
}
}
mutex_unlock(&_client_mutex);
return res;
}
static inline bool _dns_server_uri_isset(void)
{
return _uri[0] != '\0';
}
#if IS_USED(MODULE_GCOAP_DTLS)
static void _remove_cred(sock_dtls_t *sock, _cred_t *cred)
{
sock_dtls_remove_credential(sock, cred->tag);
credman_delete(cred->tag, cred->type);
cred->type = CREDMAN_TYPE_EMPTY;
cred->tag = CREDMAN_TAG_EMPTY;
}
#endif
static inline bool _is_proxied(void)
{
return IS_USED(MODULE_GCOAP_DNS_PROXIED) && _proxy[0] != '\0';
}
static int _add_init_block2_opt(coap_pkt_t *pdu)
{
if (CONFIG_GCOAP_DNS_PDU_BUF_SIZE < CONFIG_DNS_MSG_LEN) {
/* If our largest DNS message fits in the DNS PDU BUF, there is no point in sending Block2
* in the initial message---a huge response might still overwhelm our PDU buffer, but if
* that happens we could not have processed it as a DNS message if it came in in fragments
* either. */
coap_block1_t block;
coap_block_object_init(&block, 0, CONFIG_GCOAP_DNS_BLOCK_SIZE, 0);
return coap_opt_add_block2_control(pdu, &block);
}
return 0;
}
static int _add_proxy_uri_opt(coap_pkt_t *pdu, const char *proxy_uri)
{
if (_is_proxied()) {
return coap_opt_add_proxy_uri(pdu, proxy_uri);
}
return 0;
}
static int _add_req_tag_opt(coap_pkt_t *pdu, _req_ctx_t *context)
{
#if IS_USED(MODULE_GCOAP_DTLS)
if (CONFIG_GCOAP_DNS_PDU_BUF_SIZE < CONFIG_DNS_MSG_LEN) {
return coap_opt_add_opaque(pdu, 292, (uint8_t *)&context->req_tag,
sizeof(context->req_tag));
}
#else
(void)pdu;
(void)context;
#endif
return 0;
}
static int _add_remaining_options(coap_pkt_t *pdu, const char *proxy_uri, _req_ctx_t *context)
{
if (_add_proxy_uri_opt(pdu, proxy_uri) < 0) {
DEBUG("gcoap_dns: unable to add Proxy-URI option to request\n");
return -ENOBUFS;
}
/* add request tag to distinguish multiple blockwise requests on-the-air */
if (_add_req_tag_opt(pdu, context) < 0) {
DEBUG("gcoap_dns: unable to add Request-Tag option to request");
return -ENOBUFS;
}
return 0;
}
static size_t _dns_msg_compose(void *dns_buf, const char *domain_name,
int family)
{
return dns_msg_compose_query(dns_buf, domain_name, 0, family);
}
static int _set_remote(const uri_parser_result_t *uri_comp,
sock_udp_ep_t *remote) {
assert(uri_comp->host != NULL);
memset(remote, 0, sizeof(*remote));
if (uri_comp->ipv6addr != NULL) {
if (ipv6_addr_from_buf((ipv6_addr_t *)remote->addr.ipv6,
uri_comp->ipv6addr,
uri_comp->ipv6addr_len) == NULL) {
DEBUG("gcoap_dns: unable to parse IPv6 address %*.s\n",
uri_comp->ipv6addr_len, uri_comp->ipv6addr);
return -EINVAL;
}
remote->family = AF_INET6;
if (uri_comp->zoneid != NULL) {
netif_t *netif = netif_get_by_name_buffer(uri_comp->zoneid,
uri_comp->zoneid_len);
if (netif == NULL) {
DEBUG("gcoap_dns: unable to interpret zoneid %*.s\n",
uri_comp->zoneid_len, uri_comp->zoneid);
return -ENOENT;
}
remote->netif = netif_get_id(netif);
}
else {
remote->netif = SOCK_ADDR_ANY_NETIF;
}
}
else if (ipv4_addr_from_buf((ipv4_addr_t *)remote->addr.ipv4,
uri_comp->host, uri_comp->host_len) != NULL) {
remote->family = AF_INET;
}
else if (IS_USED(MODULE_SOCK_DNS)) {
char hostname[uri_comp->host_len + 1];
strncpy(hostname, uri_comp->host, uri_comp->host_len);
hostname[uri_comp->host_len] = '\0';
int res = sock_dns_query(hostname, remote->addr.ipv6, AF_UNSPEC);
if (res < 0) {
DEBUG("gcoap_dns: unable to resolve hostname %s for remote\n",
hostname);
return -EHOSTUNREACH;
}
if (res == sizeof(remote->addr.ipv4)) {
remote->family = AF_INET;
}
else if (res == sizeof(remote->addr.ipv6)) {
remote->family = AF_INET6;
}
else {
DEBUG("gcoap_dns: Unable to determine address family of hostname %s.\n",
hostname);
return -EHOSTUNREACH;
}
}
else {
DEBUG("gcoap_dns: unable to resolve hostname in URI %s for remote\n",
_uri);
return -EHOSTUNREACH;
}
if (uri_comp->port == NULL) {
if (strncmp(_uri, "coap:", sizeof("coap:") - 1) == 0) {
remote->port = CONFIG_GCOAP_PORT;
}
else {
remote->port = CONFIG_GCOAPS_PORT;
}
}
else {
char port_str[uri_comp->port_len + 1];
strncpy(port_str, uri_comp->port, uri_comp->port_len);
port_str[uri_comp->port_len] = '\0';
remote->port = atoi(port_str);
if (remote->port == 0U) {
DEBUG("gcoap_dns: invalid port %s\n", port_str);
return -EINVAL;
}
}
return 0;
}
static int _gen_uri(uri_parser_result_t *uri_comp)
{
const char *uri = (_is_proxied()) ? _proxy : _uri;
int res = uri_parser_process_string(uri_comp, uri);
if (res < 0) {
return -EINVAL;
}
return strlen(uri);
}
static ssize_t _req_init(coap_pkt_t *pdu, uri_parser_result_t *uri_comp, bool con)
{
gcoap_req_init_path_buffer(pdu, pdu->payload, pdu->payload_len, COAP_METHOD_FETCH,
uri_comp->path, uri_comp->path_len);
if (con) {
coap_hdr_set_type(pdu->hdr, COAP_TYPE_CON);
}
if (coap_opt_add_format(pdu, COAP_FORMAT_DNS_MESSAGE) < 0) {
DEBUG("gcoap_dns: unable to add Content-Format option to request\n");
}
if (coap_opt_add_accept(pdu, COAP_FORMAT_DNS_MESSAGE) < 0) {
DEBUG("gcoap_dns: unable to add Accept option to request\n");
}
if (IS_USED(MODULE_GCOAP_DTLS) &&
(uri_comp->scheme_len == (sizeof("coaps") - 1)) &&
(strncmp(uri_comp->scheme, "coaps", uri_comp->scheme_len) == 0)) {
return GCOAP_SOCKET_TYPE_DTLS;
}
return GCOAP_SOCKET_TYPE_UDP;
}
static int _do_block(coap_pkt_t *pdu, const sock_udp_ep_t *remote,
_req_ctx_t *context)
{
gcoap_socket_type_t tl_type;
ssize_t len;
bool more;
coap_block_slicer_t slicer;
coap_block_slicer_init(&slicer, context->cur_blk_num++,
CONFIG_GCOAP_DNS_BLOCK_SIZE);
tl_type = _req_init(pdu, &_uri_comp, true);
if (tl_type == GCOAP_SOCKET_TYPE_UNDEF) {
return -EINVAL;
}
more = (CONFIG_GCOAP_DNS_PDU_BUF_SIZE >= CONFIG_DNS_MSG_LEN) ||
(slicer.end < context->dns_buf_len);
if (!more && (_add_init_block2_opt(pdu) < 0)) {
DEBUG("gcoap_dns: unable to add Block2 option to request\n");
return -ENOBUFS;
}
if (coap_opt_add_block1(pdu, &slicer, context->dns_buf_len > CONFIG_GCOAP_DNS_BLOCK_SIZE) < 0) {
DEBUG("gcoap_dns: unable to add Block1 option to request\n");
return -ENOBUFS;
}
if (_add_remaining_options(pdu, _uri, context) < 0) {
return -ENOBUFS;
}
len = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD);
len += coap_blockwise_put_bytes(&slicer, pdu->payload,
context->dns_buf,
context->dns_buf_len);
coap_block1_finish(&slicer);
if ((len = _send(pdu->hdr, len, remote, slicer.start == 0, context, tl_type)) <= 0) {
printf("gcoap_dns: msg send failed: %d\n", (int)len);
return len;
}
return len;
}
static ssize_t _req(_req_ctx_t *context)
{
coap_pkt_t *pdu = context->pkt;
ssize_t len;
if ((len = _gen_uri(&_uri_comp)) < 0) {
DEBUG("gcoap_dns: unable to parse URI\n");
return len;
}
if ((len = _set_remote(&_uri_comp, &_remote)) < 0) {
return len;
}
if (context->dns_buf_len > CONFIG_GCOAP_DNS_BLOCK_SIZE) {
context->cur_blk_num = 0U;
return _do_block(pdu, &_remote, context);
}
else {
gcoap_socket_type_t tl_type = _req_init(pdu, &_uri_comp, true);
if (tl_type == GCOAP_SOCKET_TYPE_UNDEF) {
return -EINVAL;
}
if (_add_init_block2_opt(pdu) < 0) {
DEBUG("gcoap_dns: unable to add Block2 option to request\n");
return -ENOBUFS;
}
if (_add_remaining_options(pdu, _uri, context) < 0) {
DEBUG("gcoap_dns: unable to add Proxy-URI option to request\n");
return -ENOBUFS;
}
len = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD);
memcpy(pdu->payload, context->dns_buf, context->dns_buf_len);
return _send(pdu->hdr, len + context->dns_buf_len, &_remote, true, context, tl_type);
}
}
static int _dns_query(const char *domain_name, _req_ctx_t *req_ctx)
{
int res;
assert(domain_name != NULL);
if (!_dns_server_uri_isset()) {
DEBUG("gcoap_dns: no URI template provided\n");
return -ECONNREFUSED;
}
req_ctx->dns_buf_len = _dns_msg_compose(
req_ctx->dns_buf,
domain_name,
req_ctx->family
);
#if IS_USED(MODULE_GCOAP_DTLS)
req_ctx->req_tag = _req_tag++;
#endif
res = _req(req_ctx);
if (res <= 0) {
DEBUG("gcoap_dns: unable to send request\n");
if (res == 0) {
res = -ENOTRECOVERABLE;
}
}
return res;
}
static void _resp_handler(const gcoap_request_memo_t *memo, coap_pkt_t *pdu,
const sock_udp_ep_t *remote)
{
coap_block1_t block;
_req_ctx_t *context = memo->context;
void *data;
size_t data_len;
int family = context->family;
if (memo->state == GCOAP_MEMO_TIMEOUT) {
printf("gcoap_dns: CoAP request timed out\n");
context->res = -ETIMEDOUT;
goto unlock;
}
else if (memo->state != GCOAP_MEMO_RESP) {
printf("gcoap_dns: error in response\n");
context->res = -EBADMSG;
goto unlock;
}
if ((remote->port != _remote.port) ||
(remote->family != _remote.family) ||
(!ipv6_addr_is_unspecified((ipv6_addr_t *)_remote.addr.ipv6) &&
(memcmp(remote->addr.ipv6, _remote.addr.ipv6,
sizeof(_remote.addr.ipv6)) != 0))) {
DEBUG("gcoap_dns: unexpected remote for reply\n");
context->res = -EDESTADDRREQ;
goto unlock;
}
if (coap_get_code_class(pdu) != COAP_CLASS_SUCCESS) {
DEBUG("gcoap_dns: unsuccessful response: %1u.%02u\n",
coap_get_code_class(pdu), coap_get_code_detail(pdu));
context->res = -EBADMSG;
goto unlock;
}
if (coap_get_code_raw(pdu) == COAP_CODE_CONTINUE) {
int res = _do_block(pdu, remote, context);
if (res < 0) {
context->res = res;
goto unlock;
}
return;
}
if (coap_get_block2(pdu, &block)) {
uint8_t *dns_buf = context->dns_buf;
if ((block.offset + pdu->payload_len) > CONFIG_DNS_MSG_LEN) {
DEBUG("gcoap_dns: No buffer space for block-wise transfer "
"(%lu + %u) > %u\n", (long unsigned)block.offset,
pdu->payload_len, CONFIG_DNS_MSG_LEN);
context->res = -ENOBUFS;
goto unlock;
}
memcpy(&dns_buf[block.offset], pdu->payload, pdu->payload_len);
if (block.blknum == 0) {
context->dns_buf_len = pdu->payload_len;
if (block.more && strlen(_uri) > 0) {
DEBUG("gcoap_dns: Cannot complete block-wise\n");
context->res = -EINVAL;
goto unlock;
}
}
else {
context->dns_buf_len += pdu->payload_len;
}
if (block.more) {
gcoap_socket_type_t tl_type;
unsigned msg_type = coap_get_type(pdu);
int len;
tl_type = _req_init(pdu, &_uri_comp, msg_type == COAP_TYPE_ACK);
block.blknum++;
if (coap_opt_add_block2_control(pdu, &block) < 0) {
DEBUG("gcoap_dns: unable to add Block2 option to request\n");
context->res = -ENOBUFS;
goto unlock;
}
if (_add_remaining_options(pdu, _uri, context) < 0) {
context->res = -ENOBUFS;
goto unlock;
}
len = coap_opt_finish(pdu, COAP_OPT_FINISH_NONE);
if ((len = _send((uint8_t *)pdu->hdr, len, remote, false, context, tl_type)) <= 0) {
DEBUG("gcoap_dns: Unable to request next block: %d\n", len);
context->res = len;
goto unlock;
}
return;
}
data = context->dns_buf;
data_len = context->dns_buf_len;
}
else {
data = pdu->payload;
data_len = pdu->payload_len;
}
switch (coap_get_content_type(pdu)) {
case COAP_FORMAT_DNS_MESSAGE:
case COAP_FORMAT_NONE:
context->res = dns_msg_parse_reply(data, data_len, family,
context->addr_out);
if ((ENABLE_DEBUG) && (context->res < 0)) {
DEBUG("gcoap_dns: Unable to parse DNS reply: %d\n",
context->res);
}
break;
default:
context->res = -ENOMSG;
break;
}
unlock:
mutex_unlock(&context->resp_wait);
}
static ssize_t _send(const void *buf, size_t len, const sock_udp_ep_t *remote,
bool lock_resp_wait, _req_ctx_t *context, gcoap_socket_type_t tl_type)
{
if (lock_resp_wait) {
mutex_lock(&context->resp_wait);
}
return gcoap_req_send_tl(buf, len, remote, _resp_handler, context, tl_type);
}
/** @} */

33
tests/gcoap_dns/Makefile Normal file
View File

@ -0,0 +1,33 @@
include ../Makefile.tests_common
USEMODULE += embunit
USEMODULE += auto_init_gnrc_netif
USEMODULE += fmt
USEMODULE += ipv4_addr
USEMODULE += ipv6_addr
USEMODULE += gcoap_dns
USEMODULE += gnrc_ipv6_default
USEMODULE += netdev_default
USEMODULE += od
USEMODULE += shell
USEMODULE += shell_commands
COAPS ?= 1
ifeq (1,$(COAPS))
USEMODULE += gcoap_dtls
USEMODULE += prng_sha256prng
USEPKG += tinydtls
KCONFIG_ADD_CONFIG += $(CURDIR)/coaps.config
endif
CFLAGS += -DTEST_SUITES
CFLAGS += -DHAS_SOCK_DNS_MOCK=1
include $(RIOTBASE)/Makefile.include
ifeq (native,$(BOARD))
test: PORT=
$(call target-export-variables,test,PORT)
endif

View File

@ -0,0 +1,33 @@
BOARD_INSUFFICIENT_MEMORY := \
airfy-beacon \
blackpill \
bluepill-stm32f030c8 \
bluepill \
calliope-mini \
i-nucleo-lrwan1 \
im880b \
microbit \
nrf51dongle \
nrf6310 \
nucleo-f030r8 \
nucleo-f031k6 \
nucleo-f042k6 \
nucleo-f302r8 \
nucleo-f303k8 \
nucleo-f334r8 \
nucleo-l011k4 \
nucleo-l031k6 \
nucleo-l053r8 \
samd10-xmini \
saml10-xpro \
saml11-xpro \
slstk3400a \
stk3200 \
stm32f030f4-demo \
stm32f0discovery \
stm32f7508-dk \
stm32g0316-disco \
stm32l0538-disco \
stm32mp157c-dk2 \
yunjia-nrf51822 \
#

View File

@ -0,0 +1,4 @@
CONFIG_KCONFIG_USEMODULE_GCOAP=y
CONFIG_GCOAP_PDU_BUF_SIZE=256
CONFIG_KCONFIG_USEMODULE_GNRC_PKTBUF_STATIC=y
CONFIG_GNRC_PKTBUF_SIZE=3072

View File

@ -0,0 +1,6 @@
CONFIG_KCONFIG_USEMODULE_GCOAP_DNS=y
CONFIG_GCOAP_DNS_CREDS_MAX=4
CONFIG_KCONFIG_USEMODULE_CREDMAN=y
CONFIG_CREDMAN_MAX_CREDENTIALS=4
CONFIG_KCONFIG_USEMODULE_SOCK_DTLS=y
CONFIG_DTLS_CREDENTIALS_MAX=4

566
tests/gcoap_dns/main.c Normal file
View File

@ -0,0 +1,566 @@
/*
* Copyright (C) 2021 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @{
*
* @file
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "fmt.h"
#include "net/af.h"
#include "net/coap.h"
#include "net/credman.h"
#include "net/dns/msg.h"
#include "net/ipv4/addr.h"
#include "net/ipv6/addr.h"
#include "od.h"
#include "embUnit.h"
#include "shell.h"
#if IS_USED(MODULE_GCOAP_DTLS)
#include "net/sock/dtls.h"
#endif
#include "net/gcoap/dns.h"
#define PSK_ID_LEN 32U
#define PSK_LEN 32U
#define TEST_TAG 2599U
#define TEST_PSK_ID "client_identity"
#define TEST_PSK "secretPSK"
static char line_buf[SHELL_DEFAULT_BUFSIZE];
static char _psk_id[PSK_ID_LEN];
static char _psk[PSK_LEN];
static credman_credential_t _credential = {
.type = CREDMAN_TYPE_PSK,
.params = {
.psk = {
.id = { .s = _psk_id, .len = 0U, },
.key = { .s = _psk, .len = 0U, },
}
},
};
static uint8_t _mock_response[CONFIG_DNS_MSG_LEN];
static size_t _mock_response_len = 0U;
static uint8_t _resp_code = COAP_CODE_EMPTY;
#if IS_USED(MODULE_GCOAP_DTLS)
static_assert(CONFIG_GCOAP_DNS_CREDS_MAX == CONFIG_DTLS_CREDENTIALS_MAX,
"CONFIG_GCOAP_DNS_CREDS_MAX and CONFIG_DTLS_CREDENTIALS_MAX "
"must be equal for this test");
#endif
static_assert(!IS_USED(MODULE_GCOAP_DTLS) ||
(CONFIG_GCOAP_DNS_CREDS_MAX == CONFIG_CREDMAN_MAX_CREDENTIALS),
"CONFIG_GCOAP_DNS_CREDS_MAX and CONFIG_CREDMAN_MAX_CREDENTIALS "
"must be equal for this test");
#define INIT_TEST_PSK(t) \
_credential.type = CREDMAN_TYPE_PSK; \
_credential.tag = (t); \
_credential.params.psk.id.len = sizeof(TEST_PSK_ID); \
strcpy((char *)_credential.params.psk.id.s, TEST_PSK_ID); \
_credential.params.psk.key.len = sizeof(TEST_PSK); \
strcpy((char *)_credential.params.psk.key.s, TEST_PSK)
static void setup(void)
{
gcoap_dns_server_uri_set(NULL);
gcoap_dns_cred_reset();
_credential.tag = CREDMAN_TAG_EMPTY;
_credential.params.psk.id.len = 0U;
_credential.params.psk.key.len = 0U;
}
static void test_server_uri_set__success_1(void)
{
static const char uri[] = "coap://example.org/";
char res[sizeof(uri)];
TEST_ASSERT_EQUAL_INT(strlen(uri), gcoap_dns_server_uri_set(uri));
TEST_ASSERT_EQUAL_INT(strlen(uri), gcoap_dns_server_uri_get(res, sizeof(res)));
TEST_ASSERT_EQUAL_STRING(uri, res);
TEST_ASSERT(gcoap_dns_server_uri_is_set());
TEST_ASSERT_EQUAL_INT(0, gcoap_dns_server_uri_set(NULL));
TEST_ASSERT(!gcoap_dns_server_uri_is_set());
}
static void test_server_uri_set__success_2(void)
{
static const char uri[] = "coaps://example.org/";
if (IS_USED(MODULE_GCOAP_DTLS)) {
char res[sizeof(uri)];
TEST_ASSERT_EQUAL_INT(strlen(uri), gcoap_dns_server_uri_set(uri));
TEST_ASSERT_EQUAL_INT(strlen(uri), gcoap_dns_server_uri_get(res, sizeof(res)));
TEST_ASSERT_EQUAL_STRING(uri, res);
TEST_ASSERT(gcoap_dns_server_uri_is_set());
}
else {
TEST_ASSERT_EQUAL_INT(-EINVAL, gcoap_dns_server_uri_set(uri));
TEST_ASSERT(!gcoap_dns_server_uri_is_set());
}
}
static void test_server_uri_set__uri_too_long(void)
{
static const char uri[] = "coap://a.very.long.host-name.org"
"/this/is/a/very/long/path/to/dns";
TEST_ASSERT(!gcoap_dns_server_uri_is_set());
/* would not fit trailing \0 */
TEST_ASSERT_EQUAL_INT(CONFIG_GCOAP_DNS_SERVER_URI_LEN,
strlen(uri));
INIT_TEST_PSK(TEST_TAG);
TEST_ASSERT_EQUAL_INT(-ENOBUFS, gcoap_dns_server_uri_set(uri));
TEST_ASSERT(!gcoap_dns_server_uri_is_set());
}
static void test_server_uri_set__not_coap(void)
{
static const char uri[] = "https://example.org/";
TEST_ASSERT(!gcoap_dns_server_uri_is_set());
INIT_TEST_PSK(TEST_TAG);
TEST_ASSERT_EQUAL_INT(-EINVAL, gcoap_dns_server_uri_set(uri));
TEST_ASSERT(!gcoap_dns_server_uri_is_set());
}
static void test_server_uri_set__no_dns_var(void)
{
static const char uri[] = "https://example.org/";
TEST_ASSERT(!gcoap_dns_server_uri_is_set());
INIT_TEST_PSK(TEST_TAG);
TEST_ASSERT_EQUAL_INT(-EINVAL, gcoap_dns_server_uri_set(uri));
TEST_ASSERT(!gcoap_dns_server_uri_is_set());
}
static void test_server_uri_get__buf_too_short(void)
{
static const char uri[] = "coap://example.org/";
char res[sizeof(uri) - 1];
TEST_ASSERT_EQUAL_INT(strlen(uri), gcoap_dns_server_uri_set(uri));
TEST_ASSERT_EQUAL_INT(-ENOBUFS, gcoap_dns_server_uri_get(res, sizeof(res)));
}
static void test_cred_add__success(void)
{
INIT_TEST_PSK(TEST_TAG);
if (IS_USED(MODULE_GCOAP_DTLS)) {
TEST_ASSERT_EQUAL_INT(0, gcoap_dns_cred_add(&_credential));
}
else {
TEST_ASSERT_EQUAL_INT(-ENOTSUP, gcoap_dns_cred_add(&_credential));
}
}
static void test_cred_add__no_mem(void)
{
#if IS_USED(MODULE_GCOAP_DTLS)
for (unsigned i = 0; i < CONFIG_GCOAP_DNS_CREDS_MAX; i++) {
INIT_TEST_PSK(TEST_TAG + i);
TEST_ASSERT_EQUAL_INT(0, gcoap_dns_cred_add(&_credential));
}
INIT_TEST_PSK(TEST_TAG + CONFIG_GCOAP_DNS_CREDS_MAX);
TEST_ASSERT_EQUAL_INT(-ENOMEM, gcoap_dns_cred_add(&_credential));
#else
TEST_ASSERT_EQUAL_INT(-ENOTSUP, gcoap_dns_cred_add(&_credential));
#endif
}
static void test_cred_add__credman_error(void)
{
if (IS_USED(MODULE_GCOAP_DTLS)) {
INIT_TEST_PSK(TEST_TAG);
_credential.type = CREDMAN_TYPE_EMPTY;
TEST_ASSERT_EQUAL_INT(-EBADF, gcoap_dns_cred_add(&_credential));
}
else {
TEST_ASSERT_EQUAL_INT(-ENOTSUP, gcoap_dns_cred_add(&_credential));
}
}
static void test_cred_remove__success(void)
{
INIT_TEST_PSK(TEST_TAG);
if (IS_USED(MODULE_GCOAP_DTLS)) {
TEST_ASSERT_EQUAL_INT(CREDMAN_NOT_FOUND, credman_get(&_credential,
_credential.tag, _credential.type));
TEST_ASSERT_EQUAL_INT(0, gcoap_dns_cred_add(&_credential));
TEST_ASSERT_EQUAL_INT(CREDMAN_OK, credman_get(&_credential,
_credential.tag, _credential.type));
gcoap_dns_cred_remove(_credential.tag, _credential.type);
TEST_ASSERT_EQUAL_INT(CREDMAN_NOT_FOUND, credman_get(&_credential,
_credential.tag, _credential.type));
}
else {
TEST_ASSERT_EQUAL_INT(-ENOTSUP, gcoap_dns_cred_add(&_credential));
}
}
static int _unittests(int argc, char **argv)
{
(void)argc;
(void)argv;
EMB_UNIT_TESTFIXTURES(fixtures) {
new_TestFixture(test_server_uri_set__success_1),
new_TestFixture(test_server_uri_set__success_2),
new_TestFixture(test_server_uri_set__uri_too_long),
new_TestFixture(test_server_uri_set__not_coap),
new_TestFixture(test_server_uri_set__no_dns_var),
new_TestFixture(test_server_uri_get__buf_too_short),
new_TestFixture(test_cred_add__success),
new_TestFixture(test_cred_add__no_mem),
new_TestFixture(test_cred_add__credman_error),
new_TestFixture(test_cred_remove__success),
};
EMB_UNIT_TESTCALLER(gcoap_dns_tests, setup, NULL, fixtures);
TESTS_START();
TESTS_RUN((Test *)&gcoap_dns_tests);
TESTS_END();
puts("Be aware that URI and credentials are now reset.");
return 0;
}
static ssize_t _mock_dns_server(coap_pkt_t *pdu, uint8_t *buf, size_t len, void *ctx)
{
(void)ctx;
if ((_resp_code >> 5) == COAP_CLASS_SUCCESS) {
gcoap_resp_init(pdu, buf, len, _resp_code);
coap_opt_add_format(pdu, COAP_FORMAT_DNS_MESSAGE);
size_t resp_len = coap_opt_finish(pdu, COAP_OPT_FINISH_PAYLOAD);
/* write the RIOT board name in the response buffer */
if (pdu->payload_len >= _mock_response_len) {
memcpy(pdu->payload, _mock_response, _mock_response_len);
return resp_len + _mock_response_len;
}
else {
puts("gcoap_cli: msg buffer too small");
return gcoap_response(pdu, buf, len, COAP_CODE_INTERNAL_SERVER_ERROR);
}
}
else {
return gcoap_response(pdu, buf, len, _resp_code);
}
}
static void _uri_usage(const char *cmd)
{
printf("usage: %s -d\n", cmd);
printf(" %s <uri>\n", cmd);
}
static int _set_uri(int argc, char **argv)
{
int res;
if ((argc > 1) && (strcmp(argv[1], "-d") == 0)) {
gcoap_dns_server_uri_set(NULL);
puts("Successfully reset URI\n");
return 0;
}
else if (argc == 1) {
static char uri[CONFIG_GCOAP_DNS_SERVER_URI_LEN];
ssize_t res;
if ((res = gcoap_dns_server_uri_get(uri, sizeof(uri))) < 0) {
errno = -res;
perror("Unable to get URI");
return 1;
}
else {
if (res > 0) {
puts(uri);
return 0;
}
else {
_uri_usage(argv[0]);
return 1;
}
return 0;
}
}
/* argc > 1 can be assumed since argc == 1 returns above */
res = gcoap_dns_server_uri_set(argv[1]);
if (res < 0) {
errno = -res;
perror("Unable to set URI");
return errno;
}
printf("Successfully added URI %s\n", argv[1]);
return 0;
}
static void _creds_usage(const char *cmd)
{
printf("usage: %s -d <cred_tag>\n", cmd);
printf(" %s <cred_tag> <psk_id> <psk>\n", cmd);
}
static int _creds(int argc, char **argv)
{
if ((argc > 2) && (strcmp(argv[1], "-d") == 0)) {
credman_tag_t tag = atoi(argv[2]);
if (tag == 0) {
_creds_usage(argv[0]);
return 1;
}
gcoap_dns_cred_remove(tag, CREDMAN_TYPE_PSK);
printf("Successfully removed credentials with tag %u\n", tag);
return 0;
}
if (argc < 4) {
_creds_usage(argv[0]);
return 1;
}
if ((_credential.tag = atoi(argv[1])) == 0) {
_creds_usage(argv[0]);
return 1;
}
if ((_credential.params.psk.id.len = strlen(argv[2])) > PSK_ID_LEN) {
printf("PSK ID too long (max. %u bytes allowed)\n", PSK_ID_LEN);
return 1;
}
if ((_credential.params.psk.key.len = strlen(argv[3])) > PSK_LEN) {
printf("PSK too long (max. %u bytes allowed)\n", PSK_LEN);
return 1;
}
_credential.type = CREDMAN_TYPE_PSK;
strcpy((char *)_credential.params.psk.id.s, argv[2]);
strcpy((char *)_credential.params.psk.key.s, argv[3]);
int res = gcoap_dns_cred_add(&_credential);
if (res < 0) {
errno = -res;
perror("Unable to add credential");
return errno;
}
printf("Successfully added creds: %u, %s, %s\n", _credential.tag, argv[2], argv[3]);
return 0;
}
static int _proxy(int argc, char **argv)
{
if (argc < 2) {
static char proxy[CONFIG_GCOAP_DNS_SERVER_URI_LEN];
int res;
if ((res = gcoap_dns_server_proxy_get(proxy, sizeof(proxy))) < 0) {
errno = -res;
perror("Unable to get URI");
return 1;
}
else {
if (res > 0) {
puts(proxy);
return 0;
}
else {
printf("usage: %s [<proxy URI>|-]\n", argv[0]);
return 1;
}
return 0;
}
}
else if (argv[1][0] == '-') {
gcoap_dns_server_proxy_reset();
puts("Successfully reset proxy URI\n");
}
else {
int res = gcoap_dns_server_proxy_set(argv[1]);
if (res < 0) {
errno = -res;
perror("Unable to set proxy URI");
return errno;
}
printf("Successfully added proxy URI %s\n", argv[1]);
}
return 0;
}
static void _query_usage(const char *cmd)
{
printf("usage: %s <hostname> [inet|inet6]\n", cmd);
}
int _parse_af(const char *family_name)
{
if (strcmp("inet6", family_name) == 0) {
return AF_INET6;
}
else if (strcmp("inet", family_name) == 0) {
return AF_INET;
}
else {
printf("Unexpected family %s\n", family_name);
return AF_UNSPEC;
}
}
int _print_addr(const char *hostname, const uint8_t *addr, int addr_len)
{
char addr_str[IPV6_ADDR_MAX_STR_LEN];
switch (addr_len) {
case sizeof(ipv4_addr_t):
printf("Hostname %s resolves to %s (IPv4)\n",
hostname, ipv4_addr_to_str(addr_str, (ipv4_addr_t *)addr,
sizeof(addr_str)));
break;
case sizeof(ipv6_addr_t):
printf("Hostname %s resolves to %s (IPv6)\n",
hostname, ipv6_addr_to_str(addr_str, (ipv6_addr_t *)addr,
sizeof(addr_str)));
break;
default:
printf("Unexpected address format resolved for hostname %s\n",
hostname);
return 1;
}
return 0;
}
static int _query(int argc, char **argv)
{
uint8_t addr_out[sizeof(ipv6_addr_t)];
const char *hostname;
int family = AF_INET6;
int res;
int offset = 0;
if (argc < 2) {
_query_usage(argv[0]);
return 1;
}
if (argc < (offset + 2)) {
_query_usage(argv[0]);
return 1;
}
if (argc == (offset + 3)) {
if ((family = _parse_af(argv[2 + offset])) == AF_UNSPEC) {
return 1;
}
}
hostname = argv[offset + 1];
res = gcoap_dns_query(hostname, addr_out, family);
if (res < 0) {
errno = -res;
perror("Unable to resolve query");
return errno;
}
return _print_addr(hostname, addr_out, res);
}
static ssize_t _copy_mock_response(const char *str)
{
bool msn = true; /* most significant nibble */
ssize_t start = _mock_response_len;
for (unsigned i = 0; i < strlen(str); i++) {
uint8_t nibble = scn_u32_hex(&str[i], 1);
if (_mock_response_len == CONFIG_DNS_MSG_LEN) {
break;
}
if (msn) {
_mock_response[_mock_response_len] = nibble << 4;
}
else {
_mock_response[_mock_response_len++] |= nibble;
}
msn = !msn;
}
if (!msn) {
_mock_response_len++;
}
if (_mock_response_len >= CONFIG_DNS_MSG_LEN) {
printf("Too many bytes added to response");
return -ENOBUFS;
}
return _mock_response_len - start;
}
static int _resp(int argc, char **argv)
{
if (argc < 2) {
od_hex_dump(_mock_response, _mock_response_len, OD_WIDTH_DEFAULT);
return 0;
}
if (argc > 2 && (strcmp(argv[1], "-c") == 0)) {
ssize_t size = _copy_mock_response(argv[2]);
if (size >= 0) {
printf("Successfully continued response\n");
od_hex_dump(&_mock_response[_mock_response_len - size], size,
OD_WIDTH_DEFAULT);
return 0;
}
else {
return 1;
}
}
_resp_code = atoi(argv[1]);
if ((argc > 2) && ((_resp_code >> 5) == 2)) {
_mock_response_len = 0;
if (_copy_mock_response(argv[2]) < 0) {
return 1;
}
printf("Successfully set response with code %u.%02u\n", _resp_code >> 5, _resp_code & 0x1f);
od_hex_dump(_mock_response, _mock_response_len, OD_WIDTH_DEFAULT);
}
else {
printf("Successfully set response code %u.%02u\n", _resp_code >> 5, _resp_code & 0x1f);
_mock_response_len = 0;
}
return 0;
}
static const coap_resource_t _resources[] = {
{ "/", COAP_FETCH, _mock_dns_server, NULL },
};
static gcoap_listener_t _listener = {
&_resources[0],
ARRAY_SIZE(_resources),
GCOAP_SOCKET_TYPE_UNDEF,
NULL,
NULL,
NULL
};
static const shell_command_t _shell_commands[] = {
{ "unittests", "Runs unittests", _unittests},
{ "uri", "Sets URI to DoC server", _set_uri},
{ "creds", "Adds/removes credentials for DoC server", _creds},
{ "proxy", "Sets proxy URI for DoC queries", _proxy},
{ "query", "Sends DoC query for a hostname", _query},
{ "resp", "Set static response for mock DoC server", _resp},
{ NULL, NULL, NULL }
};
int main(void)
{
gcoap_register_listener(&_listener);
shell_run(_shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
return 0;
}
/** @} */

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2021 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @{
*
* @file
* @brief Mock implementation of sock_dns
*
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#include <errno.h>
#include <string.h>
#include "net/af.h"
#include "net/ipv6/addr.h"
#include "net/sock/dns.h"
int sock_dns_query(const char *domain_name, void *addr_out, int family)
{
const ipv6_addr_t a = { {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
}
};
switch (family) {
case AF_UNSPEC:
case AF_INET6:
break;
default:
return -EAFNOSUPPORT;
}
if (strcmp(domain_name, "example.org") != 0) {
return -ENOTSUP;
}
memcpy(addr_out, &a, sizeof(a));
return sizeof(a);
}
/** @} */
/** @} */

230
tests/gcoap_dns/tests/01-run.py Executable file
View File

@ -0,0 +1,230 @@
#!/usr/bin/env python3
# Copyright (C) 2019 Freie Universität Berlin
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
import sys
import unittest
from scapy.all import DNS, DNSQR
from testrunner import check_unittests
from testrunner.unittest import PexpectTestCase
class TestGCoAPDNS(PexpectTestCase):
LOGFILE = sys.stdout
def test_embedded_unittests(self):
self.spawn.sendline("unittests")
check_unittests(self.spawn)
def test_uri_cmd(self):
self.spawn.sendline("uri")
self.spawn.expect_exact("usage: uri -d")
self.spawn.expect_exact(" uri <uri>")
self.spawn.sendline("uri https://example.org")
self.spawn.expect_exact("Invalid argument")
self.spawn.sendline("uri coap://example.org")
self.spawn.expect_exact("Successfully added URI coap://example.org")
self.spawn.sendline("uri")
self.spawn.expect_exact("coap://example.org")
self.spawn.sendline("uri -d")
self.spawn.expect_exact("Successfully reset URI")
self.spawn.sendline("uri")
self.spawn.expect_exact("usage: uri -d")
self.spawn.expect_exact(" uri <uri>")
def test_creds_cmd(self):
self.spawn.sendline("creds")
self.spawn.expect_exact("usage: creds -d <cred_tag>")
self.spawn.expect_exact(" creds <cred_tag> <psk_id> <psk>")
self.spawn.sendline("creds -d")
self.spawn.expect_exact("usage: creds -d <cred_tag>")
self.spawn.sendline("creds -d 48970")
self.spawn.expect_exact(
"Successfully removed credentials with tag 48970"
)
self.spawn.sendline("creds 0 ni8Oozoh eiyit3iF")
self.spawn.expect_exact("usage: creds -d <cred_tag>")
self.spawn.expect_exact(" creds <cred_tag> <psk_id> <psk>")
self.spawn.sendline(
"creds 35528 Jizaef3aiX4wah0ein3yaeth9Roobieyu ohk7iZae"
)
self.spawn.expect_exact("PSK ID too long (max. 32 bytes allowed)")
self.spawn.sendline(
"creds 13998 Eiquahd8 aegheT8aeng7Oht0aecha3lohWail4iej"
)
self.spawn.expect_exact("PSK too long (max. 32 bytes allowed)")
for i in range(4):
self.spawn.sendline(
"creds {} eWiu5Doh pahzu5Ie".format(52411 + i)
)
res = self.spawn.expect(
[
"Successfully added creds: {}, "
"eWiu5Doh, pahzu5Ie".format(52411 + i),
# DTLS is not compiled in
"Operation not supported",
"Not supported",
]
)
if res > 0:
return
self.spawn.sendline("creds 8132 AhXah6gu ohDahpi9")
self.spawn.expect(["Cannot allocate memory", "Not enough space"])
for i in range(4):
self.spawn.sendline("creds -d {}".format(52411 + i))
self.spawn.expect_exact(
"Successfully removed credentials with tag {}"
.format(52411 + i)
)
def test_proxy_cmd(self):
self.spawn.sendline("proxy")
self.spawn.expect_exact("usage: proxy [<proxy URI>|-]")
self.spawn.sendline("proxy coap://[2001:db8::1]/")
self.spawn.expect(["Operation not supported", "Not supported"])
self.spawn.sendline("proxy -")
self.spawn.expect_exact("Successfully reset proxy URI")
def test_resp_cmd(self):
self._set_resp(2, 2, "abcdef0123456789abcdef0123456789abcdef")
self._set_resp(4, 2, "abcdef")
self._set_resp(0, 0)
self._set_resp(2, 1, DNS())
self._set_resp(
2,
1,
DNS(
qr=1,
qd=[DNSQR(qname="example.org", qtype="AAAA")],
ancount=1, # ancount needs to be set since `an` is already encoded
an=(
# already encoding
# [DNSRR(ttl=300, type="AAAA", rdata="2001:db8::1")]
# to make older scapy version on Murdock happy
b"\x00\x00\x1c\x00\x01\x00\x00\x01,\x00\x10 \x01\r\xb8\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
),
),
)
self._set_resp(0, 0)
def test_doc_success(self):
self._set_resp(
2,
1,
DNS(
qr=1,
qd=[DNSQR(qname="example.org", qtype="AAAA")],
ancount=1, # ancount needs to be set since `an` is already encoded
an=(
# already encoding
# [DNSRR(ttl=300, type="AAAA", rdata="2001:db8::1")]
# to make older scapy version on Murdock happy
b"\x00\x00\x1c\x00\x01\x00\x00\x01,\x00\x10 \x01\r\xb8\x00\x00"
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
),
),
)
self.spawn.sendline("uri coap://[::1]")
self.spawn.expect_exact("Successfully added URI coap://[::1]")
self.spawn.sendline("query example.org")
self.spawn.expect_exact(
"Hostname example.org resolves to 2001:db8::1 (IPv6)"
)
self.spawn.sendline("query example.org inet6")
self.spawn.expect_exact(
"Hostname example.org resolves to 2001:db8::1 (IPv6)"
)
self.spawn.sendline("query example.org inet")
self.spawn.expect_exact("Bad message")
self._set_resp(
2,
1,
DNS(
qr=1,
qd=[DNSQR(qname="example.org", qtype="A")],
ancount=1, # ancount needs to be set since `an` is already encoded
an=(
# already encoding
# [DNSRR(ttl=300, type="A", rdata="192.0.0.1")]
# to make older scapy version on Murdock happy
b"\x00\x00\x01\x00\x01\x00\x00\x01,\x00\x04\xc0\x00\x00\x01"
),
),
)
self.spawn.sendline("query example.org inet")
self.spawn.expect_exact(
"Hostname example.org resolves to 192.0.0.1 (IPv4)"
)
self.spawn.sendline("query example.org inet6")
self.spawn.expect_exact("Bad message")
def _expect_od_dump_of(self, hexbytes):
for i in range((len(hexbytes) // 32) + 1):
rang = hexbytes[(i * 32) : ((i * 32) + 32)] # noqa: E203
od_bytes = "".join(
[
" {}{}".format(a.upper(), b.upper())
for a, b in zip(rang[::2], rang[1::2])
]
)
self.spawn.expect_exact("{:08x}{}".format(i * 16, od_bytes))
def _check_resp(self, resp_dns):
if resp_dns is None:
resp_hex = ""
elif isinstance(resp_dns, str):
resp_hex = resp_dns
else:
self.assertIsInstance(resp_dns, DNS)
resp_hex = bytes(resp_dns).hex()
self.spawn.sendline("resp")
self._expect_od_dump_of(resp_hex)
def _set_resp(self, resp_class, resp_detail, resp_dns=None):
if resp_dns is None:
resp_hex = ""
elif isinstance(resp_dns, str):
resp_hex = resp_dns
else:
self.assertIsInstance(resp_dns, DNS)
resp_hex = bytes(resp_dns).hex()
self.assertEqual(0, resp_class & ~0x7)
self.assertEqual(0, resp_detail & ~0x1F)
if resp_hex and resp_class == 2:
MAX = 50
self.spawn.sendline(
"resp {} {}".format(
(resp_class << 5) | resp_detail,
resp_hex[:MAX]
)
)
self.spawn.expect_exact(
"Successfully set response with code "
"{:d}.{:02d}".format(resp_class, resp_detail)
)
self._expect_od_dump_of(resp_hex[:MAX])
while resp_hex[MAX:]:
resp_hex = resp_hex[MAX:]
self.spawn.sendline("resp -c {}".format(resp_hex[:MAX]))
self.spawn.expect_exact("Successfully continued response")
self._expect_od_dump_of(resp_hex[:MAX])
self._check_resp(resp_dns)
else:
self.spawn.sendline(
"resp {}".format((resp_class << 5) | resp_detail)
)
self.spawn.expect_exact(
"Successfully set response code "
"{:d}.{:02d}".format(resp_class, resp_detail)
)
if __name__ == "__main__":
unittest.main(verbosity=2)