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

gcoap_dns: initial import of a DNS over CoAP client

This commit is contained in:
Martine Lenders 2021-07-21 16:10:37 +02:00
parent dd6b473a0f
commit 107a2a0753
No known key found for this signature in database
GPG Key ID: 2134D77A5336DD80
6 changed files with 1001 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

@ -667,6 +667,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);
}
/** @} */