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:
parent
dd6b473a0f
commit
107a2a0753
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
/** @} */
|
Loading…
Reference in New Issue
Block a user