zephyr/subsys/bluetooth/mesh/rpl.c
Trond Einar Snekvik 4fbd0cb0ca Bluetooth: Mesh: Perform replay check on SeqAuth
To prevent the transport layer from accepting duplicate or out of order
segmented messages, add an RPL-like check for the SeqAuth of the
segmented messages when their context is allocated. This prevents
duplicate receives of the same segmented messages in the case where a
single source address sends two segmented messages in parallel (to two
different addresses):

Previously, when receiving two segmented messages, the first message
would go through to the access layer, then the second. Then, if the
transport layer received any repeated segments for the first message, it
would fail to identify the SeqAuth as old, as all its segments were of
new sequence numbers, and the "already complete SDU" check would only
look at the second message. Thus, the segmented message got processed
again and passed to the access layer, even though it was a duplicate.

To solve this, we need a mechanism like RPL, but only for the segmented
messages' SeqAuth. We cannot re-use the actual RPL mechanism, as it
can't support the scenario provoked by the "blocking tx" mechanism in
transport. This mechanism allocates the SeqAuth when the message is
first passed to the transport layer. The ongoing message that caused the
block would keep sending segments with higher sequence numbers than
the blocked message got, which will cause the blocked message to fail
the RPL check.

This patch adds a parallel SeqAuth mechanism to the RPL module, which
only deals with the SeqAuth of the segmented messages. This list gets
checked when the segmented message is first allocated, in the same
manner as the general RPL mechanism. The storage gets hooked into the
RPL mechanism, by adding a separate seg field to each RPL entry.

Signed-off-by: Trond Einar Snekvik <Trond.Einar.Snekvik@nordicsemi.no>
2021-03-11 12:36:04 +02:00

315 lines
6.1 KiB
C

/* Bluetooth Mesh */
/*
* Copyright (c) 2017 Intel Corporation
* Copyright (c) 2020 Lingao Meng
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <sys/atomic.h>
#include <sys/util.h>
#include <sys/byteorder.h>
#include <net/buf.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/conn.h>
#include <bluetooth/mesh.h>
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_MESH_DEBUG_RPL)
#define LOG_MODULE_NAME bt_mesh_rpl
#include "common/log.h"
#include "mesh.h"
#include "adv.h"
#include "net.h"
#include "rpl.h"
#include "settings.h"
/* Replay Protection List information for persistent storage. */
struct rpl_val {
uint32_t seq:24,
old_iv:1;
};
static struct bt_mesh_rpl replay_list[CONFIG_BT_MESH_CRPL];
static ATOMIC_DEFINE(store, CONFIG_BT_MESH_CRPL);
static inline int rpl_idx(const struct bt_mesh_rpl *rpl)
{
return rpl - &replay_list[0];
}
static void schedule_rpl_store(struct bt_mesh_rpl *entry)
{
atomic_set_bit(store, rpl_idx(entry));
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_RPL_PENDING);
}
static void schedule_rpl_clear(void)
{
bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_RPL_PENDING);
}
void bt_mesh_rpl_update(struct bt_mesh_rpl *rpl,
struct bt_mesh_net_rx *rx)
{
/* If this is the first message on the new IV index, we should reset it
* to zero to avoid invalid combinations of IV index and seg.
*/
if (rpl->old_iv && !rx->old_iv) {
rpl->seg = 0;
}
rpl->src = rx->ctx.addr;
rpl->seq = rx->seq;
rpl->old_iv = rx->old_iv;
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
schedule_rpl_store(rpl);
}
}
/* Check the Replay Protection List for a replay attempt. If non-NULL match
* parameter is given the RPL slot is returned but it is not immediately
* updated (needed for segmented messages), whereas if a NULL match is given
* the RPL is immediately updated (used for unsegmented messages).
*/
bool bt_mesh_rpl_check(struct bt_mesh_net_rx *rx,
struct bt_mesh_rpl **match)
{
int i;
/* Don't bother checking messages from ourselves */
if (rx->net_if == BT_MESH_NET_IF_LOCAL) {
return false;
}
/* The RPL is used only for the local node */
if (!rx->local_match) {
return false;
}
for (i = 0; i < ARRAY_SIZE(replay_list); i++) {
struct bt_mesh_rpl *rpl = &replay_list[i];
/* Empty slot */
if (!rpl->src) {
if (match) {
*match = rpl;
} else {
bt_mesh_rpl_update(rpl, rx);
}
return false;
}
/* Existing slot for given address */
if (rpl->src == rx->ctx.addr) {
if (rx->old_iv && !rpl->old_iv) {
return true;
}
if ((!rx->old_iv && rpl->old_iv) ||
rpl->seq < rx->seq) {
if (match) {
*match = rpl;
} else {
bt_mesh_rpl_update(rpl, rx);
}
return false;
} else {
return true;
}
}
}
BT_ERR("RPL is full!");
return true;
}
void bt_mesh_rpl_clear(void)
{
BT_DBG("");
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
schedule_rpl_clear();
} else {
(void)memset(replay_list, 0, sizeof(replay_list));
}
}
static struct bt_mesh_rpl *bt_mesh_rpl_find(uint16_t src)
{
int i;
for (i = 0; i < ARRAY_SIZE(replay_list); i++) {
if (replay_list[i].src == src) {
return &replay_list[i];
}
}
return NULL;
}
static struct bt_mesh_rpl *bt_mesh_rpl_alloc(uint16_t src)
{
int i;
for (i = 0; i < ARRAY_SIZE(replay_list); i++) {
if (!replay_list[i].src) {
replay_list[i].src = src;
return &replay_list[i];
}
}
return NULL;
}
void bt_mesh_rpl_reset(void)
{
int i;
/* Discard "old old" IV Index entries from RPL and flag
* any other ones (which are valid) as old.
*/
for (i = 0; i < ARRAY_SIZE(replay_list); i++) {
struct bt_mesh_rpl *rpl = &replay_list[i];
if (rpl->src) {
if (rpl->old_iv) {
(void)memset(rpl, 0, sizeof(*rpl));
} else {
rpl->old_iv = true;
}
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
schedule_rpl_store(rpl);
}
}
}
}
static int rpl_set(const char *name, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
struct bt_mesh_rpl *entry;
struct rpl_val rpl;
int err;
uint16_t src;
if (!name) {
BT_ERR("Insufficient number of arguments");
return -ENOENT;
}
src = strtol(name, NULL, 16);
entry = bt_mesh_rpl_find(src);
if (len_rd == 0) {
BT_DBG("val (null)");
if (entry) {
(void)memset(entry, 0, sizeof(*entry));
} else {
BT_WARN("Unable to find RPL entry for 0x%04x", src);
}
return 0;
}
if (!entry) {
entry = bt_mesh_rpl_alloc(src);
if (!entry) {
BT_ERR("Unable to allocate RPL entry for 0x%04x", src);
return -ENOMEM;
}
}
err = bt_mesh_settings_set(read_cb, cb_arg, &rpl, sizeof(rpl));
if (err) {
BT_ERR("Failed to set `net`");
return err;
}
entry->seq = rpl.seq;
entry->old_iv = rpl.old_iv;
BT_DBG("RPL entry for 0x%04x: Seq 0x%06x old_iv %u", entry->src,
entry->seq, entry->old_iv);
return 0;
}
BT_MESH_SETTINGS_DEFINE(rpl, "RPL", rpl_set);
static void store_rpl(struct bt_mesh_rpl *entry)
{
struct rpl_val rpl;
char path[18];
int err;
BT_DBG("src 0x%04x seq 0x%06x old_iv %u", entry->src, entry->seq,
entry->old_iv);
rpl.seq = entry->seq;
rpl.old_iv = entry->old_iv;
snprintk(path, sizeof(path), "bt/mesh/RPL/%x", entry->src);
err = settings_save_one(path, &rpl, sizeof(rpl));
if (err) {
BT_ERR("Failed to store RPL %s value", log_strdup(path));
} else {
BT_DBG("Stored RPL %s value", log_strdup(path));
}
}
static void clear_rpl(struct bt_mesh_rpl *rpl)
{
int err;
char path[18];
if (!rpl->src) {
return;
}
snprintk(path, sizeof(path), "bt/mesh/RPL/%x", rpl->src);
err = settings_delete(path);
if (err) {
BT_ERR("Failed to clear RPL");
} else {
BT_DBG("Cleared RPL");
}
(void)memset(rpl, 0, sizeof(*rpl));
atomic_clear_bit(store, rpl_idx(rpl));
}
static void store_pending_rpl(struct bt_mesh_rpl *rpl)
{
BT_DBG("");
if (atomic_test_and_clear_bit(store, rpl_idx(rpl))) {
store_rpl(rpl);
}
}
void bt_mesh_rpl_pending_store(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(replay_list); i++) {
if (atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) {
store_pending_rpl(&replay_list[i]);
} else {
clear_rpl(&replay_list[i]);
}
}
}