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

Merge pull request #15549 from janosbrodbeck/gcoap/pr/dtls

net/gcoap: support DTLS
This commit is contained in:
benpicco 2021-07-07 15:34:55 +02:00 committed by GitHub
commit eacbaf5295
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1022 additions and 21 deletions

View File

@ -30,6 +30,28 @@
#define ENABLE_DEBUG 0
#include "debug.h"
#if IS_USED(MODULE_GCOAP_DTLS)
#include "net/credman.h"
#include "net/dsm.h"
#include "tinydtls_keys.h"
/* Example credential tag for credman. Tag together with the credential type needs to be unique. */
#define GCOAP_DTLS_CREDENTIAL_TAG 10
static const uint8_t psk_id_0[] = PSK_DEFAULT_IDENTITY;
static const uint8_t psk_key_0[] = PSK_DEFAULT_KEY;
static const credman_credential_t credential = {
.type = CREDMAN_TYPE_PSK,
.tag = GCOAP_DTLS_CREDENTIAL_TAG,
.params = {
.psk = {
.key = { .s = psk_key_0, .len = sizeof(psk_key_0) - 1, },
.id = { .s = psk_id_0, .len = sizeof(psk_id_0) - 1, },
}
},
};
#endif
static bool _proxied = false;
static sock_udp_ep_t _proxy_remote;
static char proxy_uri[64];
@ -316,7 +338,16 @@ int gcoap_cli_cmd(int argc, char **argv)
if (strcmp(argv[1], "info") == 0) {
uint8_t open_reqs = gcoap_op_state();
printf("CoAP server is listening on port %u\n", CONFIG_GCOAP_PORT);
if (IS_USED(MODULE_GCOAP_DTLS)) {
printf("CoAP server is listening on port %u\n", CONFIG_GCOAPS_PORT);
} else {
printf("CoAP server is listening on port %u\n", CONFIG_GCOAP_PORT);
}
#if IS_USED(MODULE_GCOAP_DTLS)
printf("Connection secured with DTLS\n");
printf("Free DTLS session slots: %d/%d\n", dsm_get_num_available_slots(),
dsm_get_num_maximum_slots());
#endif
printf(" CLI requests sent: %u\n", req_count);
printf("CoAP open requests: %u\n", open_reqs);
printf("Configured Proxy: ");
@ -466,5 +497,19 @@ int gcoap_cli_cmd(int argc, char **argv)
void gcoap_cli_init(void)
{
#if IS_USED(MODULE_GCOAP_DTLS)
int res = credman_add(&credential);
if (res < 0 && res != CREDMAN_EXIST) {
/* ignore duplicate credentials */
printf("gcoap: cannot add credential to system: %d\n", res);
return;
}
sock_dtls_t *gcoap_sock_dtls = gcoap_get_sock_dtls();
res = sock_dtls_add_credential(gcoap_sock_dtls, GCOAP_DTLS_CREDENTIAL_TAG);
if (res < 0) {
printf("gcoap: cannot add credential to DTLS sock: %d\n", res);
}
#endif
gcoap_register_listener(&_listener);
}

View File

@ -0,0 +1,85 @@
# Default Makefile, for host native GNRC-based networking
# name of your application
APPLICATION = gcoap_example
# If no BOARD is found in the environment, use this default:
BOARD ?= native
# This has to be the absolute path to the RIOT base directory:
RIOTBASE ?= $(CURDIR)/../..
# Include packages that pull up and auto-init the link layer.
# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present
USEMODULE += gnrc_netdev_default
USEMODULE += auto_init_gnrc_netif
# Specify the mandatory networking modules
USEMODULE += gnrc_ipv6_default
USEMODULE += gcoap
# Additional networking modules that can be dropped if not needed
USEMODULE += gnrc_icmpv6_echo
# Required by gcoap example
USEMODULE += od
USEMODULE += fmt
# Add also the shell, some shell commands
USEMODULE += shell
USEMODULE += shell_commands
USEMODULE += ps
# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
DEVELHELP ?= 1
# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1
# Enables DTLS-secured CoAP messaging
GCOAP_ENABLE_DTLS ?= 1
ifeq (1,$(GCOAP_ENABLE_DTLS))
# Required by DTLS. Currently, only tinyDTLS is supported by sock_dtls.
USEPKG += tinydtls
USEMODULE += sock_dtls
USEMODULE += tinydtls_sock_dtls
USEMODULE += gcoap_dtls
# tinydtls needs crypto secure PRNG
USEMODULE += prng_sha1prng
# Maximum number of DTLS sessions
CFLAGS += -DDTLS_PEER_MAX=1
endif
# Instead of simulating an Ethernet connection, we can also simulate
# an IEEE 802.15.4 radio using ZEP
USE_ZEP ?= 0
# set the ZEP port for native
ZEP_PORT_BASE ?= 17754
ifeq (1,$(USE_ZEP))
TERMFLAGS += -z [::1]:$(ZEP_PORT_BASE)
USEMODULE += socket_zep
ifneq (,$(ZEP_MAC))
TERMFLAGS += --eui64=$(ZEP_MAC)
endif
endif
include $(RIOTBASE)/Makefile.include
# For now this goes after the inclusion of Makefile.include so Kconfig symbols
# are available. Only set configuration via CFLAGS if Kconfig is not being used
# for this module.
ifndef CONFIG_KCONFIG_MODULE_GCOAP
## Uncomment to redefine port, for example use 61616 for RFC 6282 UDP compression.
#GCOAP_PORT = 5683
#CFLAGS += -DCONFIG_GCOAP_PORT=$(GCOAP_PORT)
## Uncomment to redefine request token length, max 8.
#GCOAP_TOKENLEN = 2
#CFLAGS += -DCONFIG_GCOAP_TOKENLEN=$(GCOAP_TOKENLEN)
# Increase from default for confirmable block2 follow-on requests
GCOAP_RESEND_BUFS_MAX ?= 2
CFLAGS += -DCONFIG_GCOAP_RESEND_BUFS_MAX=$(GCOAP_RESEND_BUFS_MAX)
endif

View File

@ -0,0 +1,53 @@
BOARD_INSUFFICIENT_MEMORY := \
airfy-beacon \
blackpill \
bluepill \
hifive1 \
hifive1b \
im880b \
microbit \
nrf51dongle \
nrf6310 \
nucleo-f070rb \
nucleo-f072rb \
nucleo-f302r8 \
saml10-xpro \
saml11-xpro \
stm32mp157c-dk2 \
yunjia-nrf51822 \
arduino-duemilanove \
arduino-leonardo \
arduino-mega2560 \
arduino-nano \
arduino-uno \
atmega1284p \
atmega328p \
atmega328p-xplained-mini \
atxmega-a1u-xpro \
atxmega-a3bu-xplained \
bluepill-stm32f030c8 \
derfmega128 \
i-nucleo-lrwan1 \
mega-xplained \
microduino-corerf \
msb-430 \
msb-430h \
nucleo-f030r8 \
nucleo-f031k6 \
nucleo-f042k6 \
nucleo-f303k8 \
nucleo-f334r8 \
nucleo-l011k4 \
nucleo-l031k6 \
nucleo-l053r8 \
samd10-xmini \
slstk3400a \
stk3200 \
stm32f030f4-demo \
stm32f0discovery \
stm32l0538-disco \
telosb \
waspmote-pro \
z1 \
zigduino \
#

View File

@ -0,0 +1 @@
../gcoap/Makefile.slip

View File

@ -0,0 +1,12 @@
## About
This is an additional gcoap example, but with enabled DTLS. It only provides a
custom configured makefile, while the code is a symlink to the original gcoap
example. Therefore, the infos and usage notes of the other README also applies to this
example.
Please note, that with DTLS the default port is 5684 and not 5683, thus CoAP requests
must be sent to this port.
Since DTLS has higher memory and and ROM requirements, more boards are blacklisted
for this example compared to the non-DTLS gcoap example.

View File

@ -0,0 +1 @@
../gcoap/gcoap_cli.c

1
examples/gcoap_dtls/main.c Symbolic link
View File

@ -0,0 +1 @@
../gcoap/main.c

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2018 Inria
*
* 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.
*/
/**
* @ingroup examples
* @{
*
* @file
* @brief PSK and RPK keys for the dtls-sock example.
*
* @author Raul Fuentes <raul.fuentes-samaniego@inria.fr>
*
* @}
*/
#ifndef TINYDTLS_KEYS_H
#define TINYDTLS_KEYS_H
#ifdef __cplusplus
extern "C" {
#endif
/*
* Default keys examples for tinyDTLS (for RIOT, Linux and Contiki)
*/
#define PSK_DEFAULT_IDENTITY "Client_identity"
#define PSK_DEFAULT_KEY "secretPSK"
#define PSK_OPTIONS "i:k:"
#define PSK_ID_MAXLEN 32
#define PSK_MAXLEN 32
#ifdef CONFIG_DTLS_ECC
static const unsigned char ecdsa_priv_key[] = {
0x41, 0xC1, 0xCB, 0x6B, 0x51, 0x24, 0x7A, 0x14,
0x43, 0x21, 0x43, 0x5B, 0x7A, 0x80, 0xE7, 0x14,
0x89, 0x6A, 0x33, 0xBB, 0xAD, 0x72, 0x94, 0xCA,
0x40, 0x14, 0x55, 0xA1, 0x94, 0xA9, 0x49, 0xFA
};
static const unsigned char ecdsa_pub_key_x[] = {
0x36, 0xDF, 0xE2, 0xC6, 0xF9, 0xF2, 0xED, 0x29,
0xDA, 0x0A, 0x9A, 0x8F, 0x62, 0x68, 0x4E, 0x91,
0x63, 0x75, 0xBA, 0x10, 0x30, 0x0C, 0x28, 0xC5,
0xE4, 0x7C, 0xFB, 0xF2, 0x5F, 0xA5, 0x8F, 0x52
};
static const unsigned char ecdsa_pub_key_y[] = {
0x71, 0xA0, 0xD4, 0xFC, 0xDE, 0x1A, 0xB8, 0x78,
0x5A, 0x3C, 0x78, 0x69, 0x35, 0xA7, 0xCF, 0xAB,
0xE9, 0x3F, 0x98, 0x72, 0x09, 0xDA, 0xED, 0x0B,
0x4F, 0xAB, 0xC3, 0x6F, 0xC7, 0x72, 0xF8, 0x29
};
#endif /* CONFIG_DTLS_ECC */
#ifdef __cplusplus
}
#endif
#endif /* TINYDTLS_KEYS_H */

View File

@ -32,6 +32,7 @@ PSEUDOMODULES += event_timeout_ztimer
PSEUDOMODULES += evtimer_mbox
PSEUDOMODULES += evtimer_on_ztimer
PSEUDOMODULES += fmt_%
PSEUDOMODULES += gcoap_dtls
PSEUDOMODULES += gnrc_dhcpv6_%
PSEUDOMODULES += gnrc_dhcpv6_client_mud_url
PSEUDOMODULES += gnrc_ipv6_default

View File

@ -38,6 +38,9 @@ endif
ifneq (,$(filter dhcpv6,$(USEMODULE)))
DIRS += net/application_layer/dhcpv6
endif
ifneq (,$(filter dsm,$(USEMODULE)))
DIRS += net/dsm
endif
ifneq (,$(filter dummy_thread,$(USEMODULE)))
DIRS += test_utils/dummy_thread
endif

View File

@ -501,6 +501,15 @@ ifneq (,$(filter l2filter_%,$(USEMODULE)))
USEMODULE += l2filter
endif
ifneq (,$(filter gcoap_dtls,$(USEMODULE)))
USEMODULE += gcoap
USEMODULE += dsm
USEMODULE += sock_async_event
USEMODULE += sock_dtls
USEMODULE += event_thread
USEMODULE += event_timeout
endif
ifneq (,$(filter gcoap,$(USEMODULE)))
USEMODULE += nanocoap
USEMODULE += sock_async_event

View File

@ -179,6 +179,11 @@ void auto_init(void)
extern void auto_init_loramac(void);
auto_init_loramac();
}
if (IS_USED(MODULE_DSM)) {
LOG_DEBUG("Auto init dsm.\n");
extern void dsm_init(void);
dsm_init();
}
/* initialize USB devices */
if (IS_USED(MODULE_AUTO_INIT_USBUS)) {

123
sys/include/net/dsm.h Normal file
View File

@ -0,0 +1,123 @@
/*
* Copyright (C) 2021 ML!PA Consulting GmbH
*
* 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_dsm DTLS Session Management (DSM)
* @ingroup net net_dtls
* @brief This module provides functionality to store and retrieve session
* information of DTLS connections.
*
* dsm allows to store necessary session information so that not every application
* has to provide the potentially maximum number of possible session objects.
* Session storage can be offloaded to this generic module.
*
* @{\
*
* @file
* @brief DTLS session management module definition
*
* @note This module does not accept or close DTLS sessions, it merely
* provides a place to store session objects.
*
* @author János Brodbeck <janos.brodbeck@ml-pa.com>
*/
#ifndef NET_DSM_H
#define NET_DSM_H
#include <stdint.h>
#include "net/sock/dtls.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Maximum number of maintained DTLS sessions (tinyDTLS)
*/
#ifndef DTLS_PEER_MAX
#define DTLS_PEER_MAX (1)
#endif
/**
* @brief Session management states
*/
typedef enum {
NO_SPACE = -1,
SESSION_STATE_NONE = 0,
SESSION_STATE_HANDSHAKE,
SESSION_STATE_ESTABLISHED
} dsm_state_t;
/**
* @brief Initialize the DTLS session management
*
* Must call once before first use.
*/
void dsm_init(void);
/**
* @brief Stores a session
*
* Stores a given session in the internal storage of the session management.
* If the session is already stored only the state will be updated when the session
* gets established.
*
* @param[in] sock @ref sock_dtls_t, which the session is created on
* @param[in] session Session to store
* @param[in] new_state New state of the session
* @param[in] restore Indicates, whether the session object should be restored
* when an already established session is found
*
* @return Previous state of the session. If no session existed before it returns
* SESSION_STATE_NONE. If no space is available it returns NO_SPACE.
*/
dsm_state_t dsm_store(sock_dtls_t *sock, sock_dtls_session_t *session,
dsm_state_t new_state, bool restore);
/**
* @brief Removes a session
*
* Removes a given session in the internal storage of the session management.
*
* @param[in] sock @ref sock_dtls_t, which the session is created on
* @param[in] session Session to store
*/
void dsm_remove(sock_dtls_t *sock, sock_dtls_session_t *session);
/**
* @brief Returns the maximum number of sessions slots
*
* @return Number of session slots.
*/
uint8_t dsm_get_num_maximum_slots(void);
/**
* @brief Returns the number of available session slots
*
* @return Number of available session slots in the session management.
*/
uint8_t dsm_get_num_available_slots(void);
/**
* @brief Returns the least recently used session
*
* @param[in] sock @ref sock_dtls_t, which the session is created on
* @param[out] session Oldest used session
*
* @return 1, on success
* @return -1, when no session is stored
*/
ssize_t dsm_get_least_recently_used_session(sock_dtls_t *sock, sock_dtls_session_t *session);
#ifdef __cplusplus
}
#endif
#endif /* NET_DSM_H */
/** @} */

View File

@ -38,6 +38,7 @@
* - Observe Server Operation
* - Block Operation
* - Proxy Operation
* - DTLS for transport security
* - Implementation Notes
* - Implementation Status
*
@ -338,6 +339,22 @@
*
* Not implemented yet.
*
* ## DTLS as transport security ##
*
* Gcoap allows to use DTLS for transport security by using the @ref net_sock_dtls
* "DTLS sock API". Using the module gcoap_dtls enables the support. Gcoap
* listens for requests on CONFIG_GCOAPS_PORT, 5684 by default when DTLS is enabled.
*
* Credentials have to been configured before use. See @ref net_credman "Credman"
* and @ref net_sock_dtls_creds "DTLS sock credentials API" for credential managing.
* Access to the DTLS socket is provided by gcoap_get_sock_dtls().
*
* Gcoap includes a DTLS session management component that stores active sessions.
* By default, it tries to have CONFIG_GCOAP_DTLS_MINIMUM_AVAILABLE_SESSIONS
* session slots available to keep the server responsive. If not enough sessions
* are available the server destroys the session that has not been used for the
* longest time after CONFIG_GCOAP_DTLS_MINIMUM_AVAILABLE_SESSIONS_TIMEOUT_USEC.
*
* ## Implementation Notes ##
*
* ### Waiting for a response ###
@ -384,6 +401,9 @@
#include "event/timeout.h"
#include "net/ipv6/addr.h"
#include "net/sock/udp.h"
#if IS_USED(MODULE_GCOAP_DTLS)
#include "net/sock/dtls.h"
#endif
#include "net/nanocoap.h"
#include "xtimer.h"
@ -403,6 +423,36 @@ extern "C" {
#ifndef CONFIG_GCOAP_PORT
#define CONFIG_GCOAP_PORT (5683)
#endif
/**
* @brief Secure Server port; use RFC 7252 default if not defined
*/
#ifndef CONFIG_GCOAPS_PORT
#define CONFIG_GCOAPS_PORT (5684)
#endif
/**
* @brief Timeout for the DTLS handshake process. Set to 0 for infinite time
*/
#ifndef CONFIG_GCOAP_DTLS_HANDSHAKE_TIMEOUT_USEC
#define CONFIG_GCOAP_DTLS_HANDSHAKE_TIMEOUT_USEC (3 * US_PER_SEC)
#endif
/**
* @brief Number of minimum available sessions. If the count of available
* sessions falls below this threshold, the oldest used session will be
* closed after a timeout time. Set to 0 to deactivate this feature.
*/
#ifndef CONFIG_GCOAP_DTLS_MINIMUM_AVAILABLE_SESSIONS
#define CONFIG_GCOAP_DTLS_MINIMUM_AVAILABLE_SESSIONS (1)
#endif
/**
* @brief Timeout for freeing up a session when minimum number of available
* sessions is not given.
*/
#ifndef CONFIG_GCOAP_DTLS_MINIMUM_AVAILABLE_SESSIONS_TIMEOUT_USEC
#define CONFIG_GCOAP_DTLS_MINIMUM_AVAILABLE_SESSIONS_TIMEOUT_USEC (15 * US_PER_SEC)
#endif
/**
* @brief Size of the buffer used to build a CoAP request or response
@ -565,12 +615,20 @@ extern "C" {
/**
* @brief Stack size for module thread
* @{
*/
#ifndef GCOAP_STACK_SIZE
#define GCOAP_STACK_SIZE (THREAD_STACKSIZE_DEFAULT + DEBUG_EXTRA_STACKSIZE \
+ sizeof(coap_pkt_t))
#if IS_USED(MODULE_GCOAP_DTLS)
#define GCOAP_DTLS_EXTRA_STACKSIZE (THREAD_STACKSIZE_DEFAULT)
#else
#define GCOAP_DTLS_EXTRA_STACKSIZE (0)
#endif
#define GCOAP_STACK_SIZE (THREAD_STACKSIZE_DEFAULT + DEBUG_EXTRA_STACKSIZE \
+ sizeof(coap_pkt_t) + GCOAP_DTLS_EXTRA_STACKSIZE)
#endif
/** @} */
/**
* @ingroup net_gcoap_conf
* @brief Count of PDU buffers available for resending confirmable messages
@ -586,6 +644,7 @@ extern "C" {
*/
#define COAP_LINK_FLAG_INIT_RESLIST (1) /**< initialize as for first resource
* in a list */
/** @} */
/**
@ -719,6 +778,33 @@ typedef struct {
unsigned token_len; /**< Actual length of token attribute */
} gcoap_observe_memo_t;
/**
* @brief Coap socket types
*/
typedef enum {
COAP_SOCKET_TYPE_UNDEF = 0,
COAP_SOCKET_TYPE_UDP,
COAP_SOCKET_TYPE_DTLS
} coap_socket_type_t;
/**
* @brief Coap socket to handle multiple transport types
*/
typedef struct {
coap_socket_type_t type; /**< Type of stored socket */
union {
sock_udp_t *udp;
#if IS_USED(MODULE_GCOAP_DTLS) || defined(DOXYGEN)
sock_dtls_t *dtls;
#endif
} socket; /**< Stored socket */
#if IS_USED(MODULE_GCOAP_DTLS) || defined(DOXYGEN)
sock_dtls_session_t ctx_dtls_session; /**< Session object for the stored socket.
Used for exchanging a session between
functions. */
#endif
} coap_socket_t;
/**
* @brief Initializes the gcoap thread and device
*
@ -798,6 +884,7 @@ static inline ssize_t gcoap_request(coap_pkt_t *pdu, uint8_t *buf, size_t len,
* @param[in] context User defined context passed to the response handler
*
* @return length of the packet
* @return -ENOTCONN, if DTLS was used and session establishment failed
* @return 0 if cannot send
*/
ssize_t gcoap_req_send(const uint8_t *buf, size_t len,
@ -917,6 +1004,17 @@ int gcoap_get_resource_list(void *buf, size_t maxlen, uint8_t cf);
ssize_t gcoap_encode_link(const coap_resource_t *resource, char *buf,
size_t maxlen, coap_link_encoder_ctx_t *context);
#if IS_USED(MODULE_GCOAP_DTLS) || defined(DOXYGEN)
/**
* @brief Get the underlying DTLS socket of gcoap.
*
* Useful for managing credentials of gcoap.
*
* @return pointer to the @ref sock_dtls_t object
*/
sock_dtls_t *gcoap_get_sock_dtls(void);
#endif
#ifdef __cplusplus
}
#endif

View File

@ -13,6 +13,42 @@ menuconfig KCONFIG_USEMODULE_GCOAP
if KCONFIG_USEMODULE_GCOAP
menu "DTLS options"
config GCOAP_DTLS_CREDENTIAL_TAG
int "Credential tag"
default 5
range 0 65535
help
DTLS credential tag to determine, which credential to use for
authentication.
config GCOAP_DTLS_HANDSHAKE_TIMEOUT_USEC
int "DTLS handshake timeout in microseconds"
default 4000000
help
Time, expressed in microseconds, that the DTLS send function waits for
a handshake process and the send process itself to complete.
config GCOAP_DTLS_MINIMUM_AVAILABLE_SESSIONS
int "Minimum number of available sessions"
default 1
help
Number of sessions that should be kept free. If the count of free
sessions falls below this threshold, oldest used sessions will be closed
after a timeout time.
config GCOAP_DTLS_MINIMUM_AVAILABLE_SESSIONS_TIMEOUT_USEC
int "DTLS session slot automatic free up timeout in microseconds"
default 3000000
help
Time, expressed in microseconds. When the last session slot is occupied
this time indicates when a session will be freed up automatically.
Prevents that the server can be blocked by lack of available session
slots and not properly closed sessions.
endmenu # DTLS options
config GCOAP_PDU_BUF_SIZE
int "Request or response buffer size"
default 128
@ -91,6 +127,13 @@ config GCOAP_PORT
help
Server port, the default is the one specified in RFC 7252.
config GCOAPS_PORT
int "Secured server port"
default 5684
help
Server port for secured connections, the default is the one specified in
RFC 7252.
config GCOAP_REQ_WAITING_MAX
int "Maximum awaiting requests"
default 2

View File

@ -33,6 +33,12 @@
#include "random.h"
#include "thread.h"
#if IS_USED(MODULE_GCOAP_DTLS)
#include "net/sock/dtls.h"
#include "net/credman.h"
#include "net/dsm.h"
#endif
#define ENABLE_DEBUG 0
#include "debug.h"
@ -44,9 +50,14 @@
/* Internal functions */
static void *_event_loop(void *arg);
static void _on_sock_evt(sock_udp_t *sock, sock_async_flags_t type, void *arg);
static void _process_coap_pdu(sock_udp_t *sock, sock_udp_ep_t *remote,
static void _on_sock_udp_evt(sock_udp_t *sock, sock_async_flags_t type, void *arg);
static void _process_coap_pdu(coap_socket_t *sock, sock_udp_ep_t *remote,
uint8_t *buf, size_t len);
static void _tl_init_coap_socket(coap_socket_t *sock);
static ssize_t _tl_send(coap_socket_t *sock, const void *data, size_t len,
const sock_udp_ep_t *remote);
static ssize_t _tl_authenticate(coap_socket_t *sock, const sock_udp_ep_t *remote,
uint32_t timeout);
static ssize_t _well_known_core_handler(coap_pkt_t* pdu, uint8_t *buf, size_t len, void *ctx);
static void _cease_retransmission(gcoap_request_memo_t *memo);
static size_t _handle_req(coap_pkt_t *pdu, uint8_t *buf, size_t len,
@ -67,6 +78,11 @@ static int _request_matcher_default(gcoap_listener_t *listener,
const coap_resource_t **resource,
const coap_pkt_t *pdu);
#if IS_USED(MODULE_GCOAP_DTLS)
static void _on_sock_dtls_evt(sock_dtls_t *sock, sock_async_flags_t type, void *arg);
static void _dtls_free_up_session(void *arg);
#endif
/* Internal variables */
const coap_resource_t _default_resources[] = {
{ "/.well-known/core", COAP_GET, _well_known_core_handler, NULL },
@ -110,6 +126,17 @@ static event_queue_t _queue;
static uint8_t _listen_buf[CONFIG_GCOAP_PDU_BUF_SIZE];
static sock_udp_t _sock_udp;
#if IS_USED(MODULE_GCOAP_DTLS)
/* DTLS variables and definitions */
#define SOCK_DTLS_CLIENT_TAG (2)
static sock_dtls_t _sock_dtls;
static kernel_pid_t _auth_waiting_thread;
static event_timeout_t _dtls_session_free_up_tmout;
static event_callback_t _dtls_session_free_up_tmout_cb;
#endif
/* Event loop for gcoap _pid thread. */
static void *_event_loop(void *arg)
{
@ -119,8 +146,11 @@ static void *_event_loop(void *arg)
memset(&local, 0, sizeof(sock_udp_ep_t));
local.family = AF_INET6;
local.netif = SOCK_ADDR_ANY_NETIF;
local.port = CONFIG_GCOAP_PORT;
if (IS_USED(MODULE_GCOAP_DTLS)) {
local.port = CONFIG_GCOAPS_PORT;
} else {
local.port = CONFIG_GCOAP_PORT;
}
int res = sock_udp_create(&_sock_udp, &local, NULL, 0);
if (res < 0) {
DEBUG("gcoap: cannot create sock: %d\n", res);
@ -128,14 +158,127 @@ static void *_event_loop(void *arg)
}
event_queue_init(&_queue);
sock_udp_event_init(&_sock_udp, &_queue, _on_sock_evt, NULL);
event_loop(&_queue);
if (IS_USED(MODULE_GCOAP_DTLS)) {
#if IS_USED(MODULE_GCOAP_DTLS)
if (sock_dtls_create(&_sock_dtls, &_sock_udp,
CREDMAN_TAG_EMPTY,
SOCK_DTLS_1_2, SOCK_DTLS_SERVER) < 0) {
DEBUG("gcoap: error creating DTLS sock");
sock_udp_close(&_sock_udp);
return 0;
}
sock_dtls_event_init(&_sock_dtls, &_queue, _on_sock_dtls_evt,
NULL);
#endif
} else {
sock_udp_event_init(&_sock_udp, &_queue, _on_sock_udp_evt, NULL);
}
event_loop(&_queue);
return 0;
}
#if IS_USED(MODULE_GCOAP_DTLS)
/* Handles DTLS socket events from the event queue */
static void _on_sock_dtls_evt(sock_dtls_t *sock, sock_async_flags_t type, void *arg) {
(void)arg;
coap_socket_t socket = { .type = COAP_SOCKET_TYPE_DTLS, .socket.dtls = sock};
if (type & SOCK_ASYNC_CONN_RECV) {
ssize_t res = sock_dtls_recv(sock, &socket.ctx_dtls_session,
_listen_buf, sizeof(_listen_buf),
CONFIG_GCOAP_DTLS_HANDSHAKE_TIMEOUT_USEC);
if (res != -SOCK_DTLS_HANDSHAKE) {
DEBUG("gcoap: could not establish DTLS session: %zd\n", res);
sock_dtls_session_destroy(sock, &socket.ctx_dtls_session);
return;
}
dsm_state_t prev_state = dsm_store(sock, &socket.ctx_dtls_session,
SESSION_STATE_ESTABLISHED, false);
/* If session is already stored and the state was SESSION_STATE_HANDSHAKE
before, the handshake has been initiated internally by a gcoap client request
and another thread is waiting for the handshake. Send message to the
waiting thread to inform about established session */
if (prev_state == SESSION_STATE_HANDSHAKE) {
msg_t msg = { .type = DTLS_EVENT_CONNECTED };
msg_send(&msg, _auth_waiting_thread);
} else if (prev_state == NO_SPACE) {
/* No space in session management. Should not happen. If it occurs,
we lost track of sessions */
DEBUG("gcoap: no space in session management. We lost track of sessions!")
sock_dtls_session_destroy(sock, &socket.ctx_dtls_session);
}
/* If not enough session slots left: set timeout to free up session */
uint8_t minimum_free = CONFIG_GCOAP_DTLS_MINIMUM_AVAILABLE_SESSIONS;
if (dsm_get_num_available_slots() < minimum_free)
{
uint32_t timeout = CONFIG_GCOAP_DTLS_MINIMUM_AVAILABLE_SESSIONS_TIMEOUT_USEC;
event_callback_init(&_dtls_session_free_up_tmout_cb,
_dtls_free_up_session, NULL);
event_timeout_init(&_dtls_session_free_up_tmout, &_queue,
&_dtls_session_free_up_tmout_cb.super);
event_timeout_set(&_dtls_session_free_up_tmout, timeout);
}
}
if (type & SOCK_ASYNC_CONN_FIN) {
if (sock_dtls_get_event_session(sock, &socket.ctx_dtls_session)) {
/* Session is already destroyed, only remove it from dsm */
dsm_remove(sock, &socket.ctx_dtls_session);
} else {
puts("gcoap: A session was closed, but the corresponding session " \
"could not be retrieved from the socket!");
return;
}
sock_udp_ep_t ep;
sock_dtls_session_get_udp_ep(&socket.ctx_dtls_session, &ep);
/* Remove all memos of the concerned session. TODO: oberservable memos! */
for (int i = 0; i < CONFIG_GCOAP_REQ_WAITING_MAX; i++) {
if (_coap_state.open_reqs[i].state == GCOAP_MEMO_UNUSED) {
continue;
}
gcoap_request_memo_t *memo = &_coap_state.open_reqs[i];
if (sock_udp_ep_equal(&memo->remote_ep, &ep)) {
_expire_request(memo);
event_timeout_clear(&memo->resp_evt_tmout);
}
}
}
if (type & SOCK_ASYNC_MSG_RECV) {
ssize_t res = sock_dtls_recv(sock, &socket.ctx_dtls_session, _listen_buf,
sizeof(_listen_buf), 0);
if (res <= 0) {
DEBUG("gcoap: DTLS recv failure: %d\n", (int)res);
return;
}
sock_udp_ep_t ep;
sock_dtls_session_get_udp_ep(&socket.ctx_dtls_session, &ep);
_process_coap_pdu(&socket, &ep, _listen_buf, res);
}
}
/* Timeout function to free up a session when too many session slots are occupied */
static void _dtls_free_up_session(void *arg) {
(void)arg;
sock_dtls_session_t session;
uint8_t minimum_free = CONFIG_GCOAP_DTLS_MINIMUM_AVAILABLE_SESSIONS;
if (dsm_get_num_available_slots() < minimum_free) {
if (dsm_get_least_recently_used_session(&_sock_dtls, &session) != -1) {
/* free up session */
dsm_remove(&_sock_dtls, &session);
sock_dtls_session_destroy(&_sock_dtls, &session);
}
}
}
#endif /* MODULE_GCOAP_DTLS */
/* Handles UDP socket events from the event queue. */
static void _on_sock_evt(sock_udp_t *sock, sock_async_flags_t type, void *arg)
static void _on_sock_udp_evt(sock_udp_t *sock, sock_async_flags_t type, void *arg)
{
(void)arg;
sock_udp_ep_t remote;
@ -147,12 +290,13 @@ static void _on_sock_evt(sock_udp_t *sock, sock_async_flags_t type, void *arg)
DEBUG("gcoap: udp recv failure: %d\n", (int)res);
return;
}
_process_coap_pdu(sock, &remote, _listen_buf, res);
coap_socket_t socket = { .type = COAP_SOCKET_TYPE_UDP, .socket.udp = sock };
_process_coap_pdu(&socket, &remote, _listen_buf, res);
}
}
/* Processes and evaluates the coap pdu */
static void _process_coap_pdu(sock_udp_t *sock, sock_udp_ep_t *remote,
static void _process_coap_pdu(coap_socket_t *sock, sock_udp_ep_t *remote,
uint8_t *buf, size_t len)
{
coap_pkt_t pdu;
@ -201,7 +345,7 @@ static void _process_coap_pdu(sock_udp_t *sock, sock_udp_ep_t *remote,
size_t pdu_len = _handle_req(&pdu, _listen_buf, sizeof(_listen_buf),
remote);
if (pdu_len > 0) {
ssize_t bytes = sock_udp_send(sock, _listen_buf, pdu_len,
ssize_t bytes = _tl_send(sock, _listen_buf, pdu_len,
remote);
if (bytes <= 0) {
DEBUG("gcoap: send response failed: %d\n", (int)bytes);
@ -262,7 +406,7 @@ static void _process_coap_pdu(sock_udp_t *sock, sock_udp_ep_t *remote,
* */
pdu.hdr->ver_t_tkl &= 0xf0;
ssize_t bytes = sock_udp_send(sock, buf,
ssize_t bytes = _tl_send(sock, buf,
sizeof(coap_hdr_t), remote);
if (bytes <= 0) {
DEBUG("gcoap: empty response failed: %d\n", (int)bytes);
@ -299,8 +443,10 @@ static void _on_resp_timeout(void *arg) {
return;
}
ssize_t bytes = sock_udp_send(&_sock_udp, memo->msg.data.pdu_buf,
memo->msg.data.pdu_len, &memo->remote_ep);
coap_socket_t socket;
_tl_init_coap_socket(&socket);
ssize_t bytes = _tl_send(&socket, memo->msg.data.pdu_buf,
memo->msg.data.pdu_len, &memo->remote_ep);
if (bytes <= 0) {
DEBUG("gcoap: sock resend failed: %d\n", (int)bytes);
_expire_request(memo);
@ -732,6 +878,109 @@ static void _find_obs_memo_resource(gcoap_observe_memo_t **memo,
}
}
/*
* Transport layer functions
*/
static void _tl_init_coap_socket(coap_socket_t *sock)
{
#if IS_USED(MODULE_GCOAP_DTLS)
sock->type = COAP_SOCKET_TYPE_DTLS;
sock->socket.dtls = &_sock_dtls;
#else
sock->type = COAP_SOCKET_TYPE_UDP;
sock->socket.udp = &_sock_udp;
#endif
}
static ssize_t _tl_send(coap_socket_t *sock, const void *data, size_t len,
const sock_udp_ep_t *remote)
{
ssize_t res = -1;
if (sock->type == COAP_SOCKET_TYPE_DTLS) {
#if IS_USED(MODULE_GCOAP_DTLS)
/* prepare session */
sock_dtls_session_set_udp_ep(&sock->ctx_dtls_session, remote);
dsm_state_t session_state = dsm_store(sock->socket.dtls, &sock->ctx_dtls_session,
SESSION_STATE_HANDSHAKE, true);
if (session_state == NO_SPACE) {
return -1;
}
/* send application data */
res = sock_dtls_send(sock->socket.dtls, &sock->ctx_dtls_session, data, len,
SOCK_NO_TIMEOUT);
if (res <= 0 ) {
dsm_remove(sock->socket.dtls, &sock->ctx_dtls_session);
sock_dtls_session_destroy(sock->socket.dtls, &sock->ctx_dtls_session);
}
#endif
} else if (sock->type == COAP_SOCKET_TYPE_UDP) {
res = sock_udp_send(sock->socket.udp, data, len, remote);
} else {
DEBUG("gcoap: undefined socket type\n");
}
return res;
}
static ssize_t _tl_authenticate(coap_socket_t *sock, const sock_udp_ep_t *remote,
uint32_t timeout)
{
#if !IS_USED(MODULE_GCOAP_DTLS)
(void)sock;
(void)remote;
(void)timeout;
return 0;
#else
int res;
/* prepare session */
sock_dtls_session_set_udp_ep(&sock->ctx_dtls_session, remote);
dsm_state_t session_state = dsm_store(sock->socket.dtls, &sock->ctx_dtls_session,
SESSION_STATE_HANDSHAKE, true);
if (session_state == SESSION_STATE_ESTABLISHED) {
return 0;
}
if (session_state == NO_SPACE) {
DEBUG("gcoap: no space in dsm\n")
return -ENOTCONN;
}
/* start handshake */
_auth_waiting_thread = thread_getpid();
res = sock_dtls_session_init(sock->socket.dtls, remote, &sock->ctx_dtls_session);
if (res == 0) {
/* session already exists */
_auth_waiting_thread = -1;
return res;
}
msg_t msg;
bool is_timed_out = false;
do {
uint32_t start = xtimer_now_usec();
res = xtimer_msg_receive_timeout(&msg, timeout);
/* ensure whole timeout time for the case we receive other messages than DTLS_EVENT_CONNECTED */
if (timeout != SOCK_NO_TIMEOUT) {
uint32_t diff = (xtimer_now_usec() - start);
timeout = (diff > timeout) ? 0: timeout - diff;
is_timed_out = (res < 0) || (timeout == 0);
}
}
while (!is_timed_out && (msg.type != DTLS_EVENT_CONNECTED));
if (is_timed_out && (msg.type != DTLS_EVENT_CONNECTED)) {
DEBUG("gcoap: authentication timed out\n");
dsm_remove(sock->socket.dtls, &sock->ctx_dtls_session);
sock_dtls_session_destroy(sock->socket.dtls, &sock->ctx_dtls_session);
return -ENOTCONN;
}
return 0;
#endif
}
/*
* gcoap interface functions
*/
@ -887,8 +1136,16 @@ ssize_t gcoap_req_send(const uint8_t *buf, size_t len,
}
}
ssize_t res = 0;
coap_socket_t socket = { 0 };
_tl_init_coap_socket(&socket);
if (IS_USED(MODULE_GCOAP_DTLS) && socket.type == COAP_SOCKET_TYPE_DTLS) {
res = _tl_authenticate(&socket, remote, CONFIG_GCOAP_DTLS_HANDSHAKE_TIMEOUT_USEC);
}
/* set response timeout; may be zero for non-confirmable */
if (memo != NULL) {
if (memo != NULL && res == 0) {
if (timeout > 0) {
event_callback_init(&memo->resp_tmout_cb, _on_resp_timeout, memo);
event_timeout_init(&memo->resp_evt_tmout, &_queue,
@ -900,7 +1157,9 @@ ssize_t gcoap_req_send(const uint8_t *buf, size_t len,
}
}
ssize_t res = sock_udp_send(&_sock_udp, buf, len, remote);
if (res == 0) {
res = _tl_send(&socket, buf, len, remote);
}
if (res <= 0) {
if (memo != NULL) {
if (msg_type == COAP_TYPE_CON) {
@ -913,7 +1172,7 @@ ssize_t gcoap_req_send(const uint8_t *buf, size_t len,
}
DEBUG("gcoap: sock send failed: %d\n", (int)res);
}
return ((res > 0) ? res : 0);
return ((res > 0 || res == -ENOTCONN) ? res : 0);
}
int gcoap_resp_init(coap_pkt_t *pdu, uint8_t *buf, size_t len, unsigned code)
@ -974,11 +1233,12 @@ size_t gcoap_obs_send(const uint8_t *buf, size_t len,
const coap_resource_t *resource)
{
gcoap_observe_memo_t *memo = NULL;
coap_socket_t socket;
_tl_init_coap_socket(&socket);
_find_obs_memo_resource(&memo, resource);
if (memo) {
ssize_t bytes = sock_udp_send(&_sock_udp, buf, len, memo->observer);
ssize_t bytes = _tl_send(&socket, buf, len, memo->observer);
return (size_t)((bytes > 0) ? bytes : 0);
}
else {
@ -1067,4 +1327,13 @@ ssize_t gcoap_encode_link(const coap_resource_t *resource, char *buf,
return exp_size;
}
#if IS_USED(MODULE_GCOAP_DTLS)
sock_dtls_t *gcoap_get_sock_dtls(void)
{
return &_sock_dtls;
}
#endif
/* */
/** @} */

2
sys/net/dsm/Makefile Normal file
View File

@ -0,0 +1,2 @@
MODULE = dsm
include $(RIOTBASE)/Makefile.base

1
sys/net/dsm/Makefile.dep Normal file
View File

@ -0,0 +1 @@
FEATURES_REQUIRED += sock_dtls

186
sys/net/dsm/dsm.c Normal file
View File

@ -0,0 +1,186 @@
/*
* Copyright (C) 2021 ML!PA Consulting GmbH
*
* 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.
*/
/**
* @ingroup net_dsm
* @{
*
* @file
* @brief DTLS Session Management module implementation
*
* @author János Brodbeck <janos.brodbeck@ml-pa.com>
*
* @}
*/
#include "net/dsm.h"
#include "mutex.h"
#include "net/sock/util.h"
#include "xtimer.h"
#define ENABLE_DEBUG 0
#include "debug.h"
typedef struct {
sock_dtls_t *sock;
sock_dtls_session_t session;
dsm_state_t state;
uint32_t last_used_sec;
} dsm_session_t;
static int _find_session(sock_dtls_t *sock, sock_dtls_session_t *to_find,
dsm_session_t **session);
static mutex_t _lock;
static dsm_session_t _sessions[DTLS_PEER_MAX];
static uint8_t _available_slots;
void dsm_init(void)
{
mutex_init(&_lock);
_available_slots = DTLS_PEER_MAX;
}
dsm_state_t dsm_store(sock_dtls_t *sock, sock_dtls_session_t *session,
dsm_state_t new_state, bool restore)
{
sock_udp_ep_t ep;
dsm_session_t *session_slot = NULL;
dsm_state_t prev_state = NO_SPACE;
mutex_lock(&_lock);
ssize_t res = _find_session(sock, session, &session_slot);
if (res < 0) {
DEBUG("dsm: no space for session to store\n");
goto out;
}
prev_state = session_slot->state;
if (session_slot->state != SESSION_STATE_ESTABLISHED) {
session_slot->state = new_state;
}
/* no existing session found */
if (res == 0) {
DEBUG("dsm: no existing session found, storing as new session\n")
sock_dtls_session_get_udp_ep(session, &ep);
sock_dtls_session_set_udp_ep(&session_slot->session, &ep);
session_slot->sock = sock;
_available_slots--;
}
/* existing session found and session should be restored */
if (res == 1 && restore) {
DEBUG("dsm: existing session found, restoring\n")
memcpy(session, &session_slot->session, sizeof(sock_dtls_session_t));
}
session_slot->last_used_sec = (uint32_t)(xtimer_now_usec64() / US_PER_SEC);
out:
mutex_unlock(&_lock);
return prev_state;
}
void dsm_remove(sock_dtls_t *sock, sock_dtls_session_t *session)
{
dsm_session_t *session_slot = NULL;
mutex_lock(&_lock);
if (_find_session(sock, session, &session_slot) == 1) {
if (session_slot->state == SESSION_STATE_NONE) {
/* session has already been removed. Can happen when we remove the session
before we get the close ACK of the remote peer (e.g. force reset of peer)
and then get an ACK (= SOCK_ASYNC_CONN_FIN event) of the remote and
call this function again. */
goto out;
}
session_slot->state = SESSION_STATE_NONE;
_available_slots++;
DEBUG("dsm: removed session\n");
} else {
DEBUG("dsm: could not find session to remove, it was probably already removed\n");
}
out:
mutex_unlock(&_lock);
}
uint8_t dsm_get_num_available_slots(void)
{
return _available_slots;
}
uint8_t dsm_get_num_maximum_slots(void)
{
return DTLS_PEER_MAX;
}
ssize_t dsm_get_least_recently_used_session(sock_dtls_t *sock, sock_dtls_session_t *session)
{
int res = -1;
dsm_session_t *session_slot = NULL;
if (dsm_get_num_available_slots() == DTLS_PEER_MAX) {
return res;
}
mutex_lock(&_lock);
for (uint8_t i=0; i < DTLS_PEER_MAX; i++) {
if (_sessions[i].state != SESSION_STATE_ESTABLISHED) {
continue;
}
if (_sessions[i].sock != sock) {
continue;
}
if (session_slot == NULL ||
session_slot->last_used_sec > _sessions[i].last_used_sec) {
session_slot = &_sessions[i];
}
}
if (session_slot) {
memcpy(session, &session_slot->session, sizeof(sock_dtls_session_t));
res = 1;
}
mutex_unlock(&_lock);
return res;
}
/* Search for existing session or empty slot for new one
* Returns 1, if existing session found
* Returns 0, if empty slot found
* Returns -1, if no existing or empty session found */
static int _find_session(sock_dtls_t *sock, sock_dtls_session_t *to_find,
dsm_session_t **session)
{
/* FIXME: optimize search / data structure */
sock_udp_ep_t to_find_ep, curr_ep;
dsm_session_t *empty_session = NULL;
sock_dtls_session_get_udp_ep(to_find, &to_find_ep);
for (uint8_t i=0; i < DTLS_PEER_MAX; i++) {
if (_sessions[i].state == SESSION_STATE_NONE) {
empty_session = &_sessions[i];
continue;
}
sock_dtls_session_get_udp_ep(&_sessions[i].session, &curr_ep);
if (sock_udp_ep_equal(&curr_ep, &to_find_ep) && _sessions[i].sock == sock) {
/* found existing session */
*session = &_sessions[i];
return 1;
}
}
if (empty_session) {
*session = empty_session;
return 0;
}
return -1;
}