diff --git a/sys/Makefile b/sys/Makefile index fc5c0633c2..eade1daea3 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -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 diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index 553fad2945..a2c0d763fd 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -174,6 +174,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)) { diff --git a/sys/include/net/dsm.h b/sys/include/net/dsm.h new file mode 100644 index 0000000000..8c7c16ad96 --- /dev/null +++ b/sys/include/net/dsm.h @@ -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 + */ + +#ifndef NET_DSM_H +#define NET_DSM_H + +#include +#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 */ +/** @} */ diff --git a/sys/net/dsm/Makefile b/sys/net/dsm/Makefile new file mode 100644 index 0000000000..d343870610 --- /dev/null +++ b/sys/net/dsm/Makefile @@ -0,0 +1,2 @@ +MODULE = dsm +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/dsm/Makefile.dep b/sys/net/dsm/Makefile.dep new file mode 100644 index 0000000000..d2c6103bdc --- /dev/null +++ b/sys/net/dsm/Makefile.dep @@ -0,0 +1 @@ +FEATURES_REQUIRED += sock_dtls diff --git a/sys/net/dsm/dsm.c b/sys/net/dsm/dsm.c new file mode 100644 index 0000000000..f54602615e --- /dev/null +++ b/sys/net/dsm/dsm.c @@ -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 + * + * @} + */ + +#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; +}