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:
commit
a8254d52b8
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
219
sys/include/net/gcoap/dns.h
Normal 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 */
|
||||
/** @} */
|
@ -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"
|
||||
|
737
sys/net/application_layer/gcoap/dns.c
Normal file
737
sys/net/application_layer/gcoap/dns.c
Normal 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
33
tests/gcoap_dns/Makefile
Normal 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
|
33
tests/gcoap_dns/Makefile.ci
Normal file
33
tests/gcoap_dns/Makefile.ci
Normal 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 \
|
||||
#
|
4
tests/gcoap_dns/app.config
Normal file
4
tests/gcoap_dns/app.config
Normal 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
|
6
tests/gcoap_dns/coaps.config
Normal file
6
tests/gcoap_dns/coaps.config
Normal 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
566
tests/gcoap_dns/main.c
Normal 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;
|
||||
}
|
||||
|
||||
/** @} */
|
50
tests/gcoap_dns/sock_dns_mock.c
Normal file
50
tests/gcoap_dns/sock_dns_mock.c
Normal 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
230
tests/gcoap_dns/tests/01-run.py
Executable 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)
|
Loading…
Reference in New Issue
Block a user