Bluetooth: Mesh: Network loopback
Changes the local network interface to exclusively handle packets for the local interface, duplicating the buffers in the process. The loopback mechanism now operates its own packet pool for the local interface queue. The loopback is moved ahead of encryption, allowing the local interface packets to go back up the stack without network crypto, saving a full round of encrypt/decrypt for self-send. Packets for group addresses the local node subscribes to are now duplicated, with one unencrypted variant going into the network queue, and the network bound packets following the regular path to the advertiser. Introduces one new configuration for setting the number of loopback buffers. Signed-off-by: Trond Einar Snekvik <Trond.Einar.Snekvik@nordicsemi.no>
This commit is contained in:
parent
e3eb9bedec
commit
3c8e5b0e1c
5 changed files with 197 additions and 92 deletions
|
@ -186,7 +186,7 @@ config BT_MESH_MSG_CACHE_SIZE
|
|||
config BT_MESH_ADV_BUF_COUNT
|
||||
int "Number of advertising buffers"
|
||||
default 6
|
||||
range 3 256
|
||||
range 1 256
|
||||
help
|
||||
Number of advertising buffers available. This should be chosen
|
||||
based on what kind of features the local node should have. E.g.
|
||||
|
@ -300,6 +300,13 @@ config BT_MESH_TX_SEG_MAX
|
|||
which leaves 56 bytes for application layer data using a
|
||||
4-byte MIC and 52 bytes using an 8-byte MIC.
|
||||
|
||||
config BT_MESH_LOOPBACK_BUFS
|
||||
int "Number of loopback buffers"
|
||||
default 3
|
||||
help
|
||||
The number of buffers allocated for the network loopback mechanism.
|
||||
Loopback is used when the device sends messages to itself.
|
||||
|
||||
config BT_MESH_RELAY
|
||||
bool "Relay support"
|
||||
help
|
||||
|
|
|
@ -3535,6 +3535,8 @@ void bt_mesh_subnet_del(struct bt_mesh_subnet *sub, bool store)
|
|||
bt_mesh_friend_clear_net_idx(sub->net_idx);
|
||||
}
|
||||
|
||||
bt_mesh_net_loopback_clear(sub->net_idx);
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_SETTINGS) && store) {
|
||||
bt_mesh_clear_subnet(sub);
|
||||
}
|
||||
|
|
|
@ -178,6 +178,8 @@ void bt_mesh_reset(void)
|
|||
bt_mesh_rx_reset();
|
||||
bt_mesh_tx_reset();
|
||||
|
||||
bt_mesh_net_loopback_clear(BT_MESH_KEY_ANY);
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) {
|
||||
if (IS_ENABLED(CONFIG_BT_MESH_LPN_SUB_ALL_NODES_ADDR)) {
|
||||
uint16_t group = BT_MESH_ADDR_ALL_NODES;
|
||||
|
|
|
@ -45,6 +45,10 @@
|
|||
*/
|
||||
#define BT_MESH_NET_MIN_PDU_LEN (BT_MESH_NET_HDR_LEN + 1 + 8)
|
||||
|
||||
#define LOOPBACK_MAX_PDU_LEN (BT_MESH_NET_HDR_LEN + 16)
|
||||
#define LOOPBACK_USER_DATA_SIZE sizeof(struct bt_mesh_subnet *)
|
||||
#define LOOPBACK_BUF_SUB(buf) (*(struct bt_mesh_subnet **)net_buf_user_data(buf))
|
||||
|
||||
/* Seq limit after IV Update is triggered */
|
||||
#define IV_UPDATE_SEQ_LIMIT 8000000
|
||||
|
||||
|
@ -88,6 +92,9 @@ struct bt_mesh_net bt_mesh = {
|
|||
},
|
||||
};
|
||||
|
||||
NET_BUF_POOL_DEFINE(loopback_buf_pool, CONFIG_BT_MESH_LOOPBACK_BUFS,
|
||||
LOOPBACK_MAX_PDU_LEN, LOOPBACK_USER_DATA_SIZE, NULL);
|
||||
|
||||
static uint32_t dup_cache[CONFIG_BT_MESH_MSG_CACHE_SIZE];
|
||||
static int dup_cache_next;
|
||||
|
||||
|
@ -111,8 +118,7 @@ static bool check_dup(struct net_buf_simple *data)
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool msg_cache_match(struct bt_mesh_net_rx *rx,
|
||||
struct net_buf_simple *pdu)
|
||||
static bool msg_cache_match(struct net_buf_simple *pdu)
|
||||
{
|
||||
uint16_t i;
|
||||
|
||||
|
@ -733,19 +739,63 @@ static void bt_mesh_net_local(struct k_work *work)
|
|||
struct net_buf *buf;
|
||||
|
||||
while ((buf = net_buf_slist_get(&bt_mesh.local_queue))) {
|
||||
BT_DBG("len %u: %s", buf->len, bt_hex(buf->data, buf->len));
|
||||
bt_mesh_net_recv(&buf->b, 0, BT_MESH_NET_IF_LOCAL);
|
||||
struct bt_mesh_subnet *sub = LOOPBACK_BUF_SUB(buf);
|
||||
struct bt_mesh_net_rx rx = {
|
||||
.ctx = {
|
||||
.net_idx = sub->net_idx,
|
||||
/* Initialize AppIdx to a sane value */
|
||||
.app_idx = BT_MESH_KEY_UNUSED,
|
||||
.recv_ttl = TTL(buf->data),
|
||||
/* TTL=1 only goes to local IF */
|
||||
.send_ttl = 1U,
|
||||
.addr = SRC(buf->data),
|
||||
.recv_dst = DST(buf->data),
|
||||
.recv_rssi = 0,
|
||||
},
|
||||
.net_if = BT_MESH_NET_IF_LOCAL,
|
||||
.sub = sub,
|
||||
.old_iv = (IVI(buf->data) != (bt_mesh.iv_index & 0x01)),
|
||||
.ctl = CTL(buf->data),
|
||||
.seq = SEQ(buf->data),
|
||||
.new_key = sub->kr_flag,
|
||||
.local_match = 1U,
|
||||
.friend_match = 0U,
|
||||
};
|
||||
|
||||
BT_DBG("src: 0x%04x dst: 0x%04x seq 0x%06x sub %p", rx.ctx.addr,
|
||||
rx.ctx.addr, rx.seq, sub);
|
||||
|
||||
(void) bt_mesh_trans_recv(&buf->b, &rx);
|
||||
net_buf_unref(buf);
|
||||
}
|
||||
}
|
||||
|
||||
int bt_mesh_net_encode(struct bt_mesh_net_tx *tx, struct net_buf_simple *buf,
|
||||
bool proxy)
|
||||
static void net_tx_cred_get(struct bt_mesh_net_tx *tx, uint8_t *nid,
|
||||
const uint8_t **enc, const uint8_t **priv)
|
||||
{
|
||||
if (!IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) || !tx->friend_cred) {
|
||||
tx->friend_cred = 0U;
|
||||
*nid = tx->sub->keys[tx->sub->kr_flag].nid;
|
||||
*enc = tx->sub->keys[tx->sub->kr_flag].enc;
|
||||
*priv = tx->sub->keys[tx->sub->kr_flag].privacy;
|
||||
return;
|
||||
}
|
||||
|
||||
if (friend_cred_get(tx->sub, BT_MESH_ADDR_UNASSIGNED, nid, enc, priv)) {
|
||||
BT_WARN("Falling back to master credentials");
|
||||
|
||||
tx->friend_cred = 0U;
|
||||
|
||||
*nid = tx->sub->keys[tx->sub->kr_flag].nid;
|
||||
*enc = tx->sub->keys[tx->sub->kr_flag].enc;
|
||||
*priv = tx->sub->keys[tx->sub->kr_flag].privacy;
|
||||
}
|
||||
}
|
||||
|
||||
static int net_header_encode(struct bt_mesh_net_tx *tx, uint8_t nid,
|
||||
struct net_buf_simple *buf)
|
||||
{
|
||||
const bool ctl = (tx->ctx->app_idx == BT_MESH_KEY_UNUSED);
|
||||
uint8_t nid;
|
||||
const uint8_t *enc, *priv;
|
||||
int err;
|
||||
|
||||
if (ctl && net_buf_simple_tailroom(buf) < 8) {
|
||||
BT_ERR("Insufficient MIC space for CTL PDU");
|
||||
|
@ -768,26 +818,25 @@ int bt_mesh_net_encode(struct bt_mesh_net_tx *tx, struct net_buf_simple *buf,
|
|||
net_buf_simple_push_u8(buf, tx->ctx->send_ttl);
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) && tx->friend_cred) {
|
||||
if (friend_cred_get(tx->sub, BT_MESH_ADDR_UNASSIGNED,
|
||||
&nid, &enc, &priv)) {
|
||||
BT_WARN("Falling back to master credentials");
|
||||
|
||||
tx->friend_cred = 0U;
|
||||
|
||||
nid = tx->sub->keys[tx->sub->kr_flag].nid;
|
||||
enc = tx->sub->keys[tx->sub->kr_flag].enc;
|
||||
priv = tx->sub->keys[tx->sub->kr_flag].privacy;
|
||||
}
|
||||
} else {
|
||||
tx->friend_cred = 0U;
|
||||
nid = tx->sub->keys[tx->sub->kr_flag].nid;
|
||||
enc = tx->sub->keys[tx->sub->kr_flag].enc;
|
||||
priv = tx->sub->keys[tx->sub->kr_flag].privacy;
|
||||
}
|
||||
|
||||
net_buf_simple_push_u8(buf, (nid | (BT_MESH_NET_IVI_TX & 1) << 7));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_mesh_net_encode(struct bt_mesh_net_tx *tx, struct net_buf_simple *buf,
|
||||
bool proxy)
|
||||
{
|
||||
const uint8_t *enc, *priv;
|
||||
uint8_t nid;
|
||||
int err;
|
||||
|
||||
net_tx_cred_get(tx, &nid, &enc, &priv);
|
||||
|
||||
err = net_header_encode(tx, nid, buf);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = bt_mesh_net_encrypt(enc, buf, BT_MESH_NET_IVI_TX, proxy);
|
||||
if (err) {
|
||||
return err;
|
||||
|
@ -796,9 +845,35 @@ int bt_mesh_net_encode(struct bt_mesh_net_tx *tx, struct net_buf_simple *buf,
|
|||
return bt_mesh_net_obfuscate(buf->data, BT_MESH_NET_IVI_TX, priv);
|
||||
}
|
||||
|
||||
static int loopback(const struct bt_mesh_net_tx *tx, const uint8_t *data,
|
||||
size_t len)
|
||||
{
|
||||
struct net_buf *buf;
|
||||
|
||||
buf = net_buf_alloc(&loopback_buf_pool, K_NO_WAIT);
|
||||
if (!buf) {
|
||||
BT_WARN("Unable to allocate loopback");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
BT_DBG("");
|
||||
|
||||
LOOPBACK_BUF_SUB(buf) = tx->sub;
|
||||
|
||||
net_buf_add_mem(buf, data, len);
|
||||
|
||||
net_buf_slist_put(&bt_mesh.local_queue, buf);
|
||||
|
||||
k_work_submit(&bt_mesh.local_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bt_mesh_net_send(struct bt_mesh_net_tx *tx, struct net_buf *buf,
|
||||
const struct bt_mesh_send_cb *cb, void *cb_data)
|
||||
{
|
||||
const uint8_t *enc, *priv;
|
||||
uint8_t nid;
|
||||
int err;
|
||||
|
||||
BT_DBG("src 0x%04x dst 0x%04x len %u headroom %zu tailroom %zu",
|
||||
|
@ -811,54 +886,89 @@ int bt_mesh_net_send(struct bt_mesh_net_tx *tx, struct net_buf *buf,
|
|||
tx->ctx->send_ttl = bt_mesh_default_ttl_get();
|
||||
}
|
||||
|
||||
err = bt_mesh_net_encode(tx, &buf->b, false);
|
||||
net_tx_cred_get(tx, &nid, &enc, &priv);
|
||||
err = net_header_encode(tx, nid, &buf->b);
|
||||
if (err) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Deliver to GATT Proxy Clients if necessary. Mesh spec 3.4.5.2:
|
||||
* "The output filter of the interface connected to advertising or
|
||||
* GATT bearers shall drop all messages with TTL value set to 1."
|
||||
*/
|
||||
if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY) &&
|
||||
tx->ctx->send_ttl != 1U) {
|
||||
if (bt_mesh_proxy_relay(&buf->b, tx->ctx->addr) &&
|
||||
BT_MESH_ADDR_IS_UNICAST(tx->ctx->addr)) {
|
||||
/* Notify completion if this only went
|
||||
* through the Mesh Proxy.
|
||||
*/
|
||||
send_cb_finalize(cb, cb_data);
|
||||
|
||||
err = 0;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
/* Deliver to local network interface if necessary */
|
||||
if (bt_mesh_fixed_group_match(tx->ctx->addr) ||
|
||||
bt_mesh_elem_find(tx->ctx->addr)) {
|
||||
if (cb && cb->start) {
|
||||
cb->start(0, 0, cb_data);
|
||||
err = loopback(tx, buf->data, buf->len);
|
||||
|
||||
/* Local unicast messages should not go out to network */
|
||||
if (BT_MESH_ADDR_IS_UNICAST(tx->ctx->addr) ||
|
||||
tx->ctx->send_ttl == 1U) {
|
||||
if (!err) {
|
||||
send_cb_finalize(cb, cb_data);
|
||||
}
|
||||
|
||||
goto done;
|
||||
}
|
||||
net_buf_slist_put(&bt_mesh.local_queue, net_buf_ref(buf));
|
||||
if (cb && cb->end) {
|
||||
cb->end(0, cb_data);
|
||||
}
|
||||
k_work_submit(&bt_mesh.local_work);
|
||||
} else if (tx->ctx->send_ttl != 1U) {
|
||||
/* Deliver to to the advertising network interface. Mesh spec
|
||||
* 3.4.5.2: "The output filter of the interface connected to
|
||||
* advertising or GATT bearers shall drop all messages with
|
||||
* TTL value set to 1."
|
||||
*/
|
||||
bt_mesh_adv_send(buf, cb, cb_data);
|
||||
}
|
||||
|
||||
/* Mesh spec 3.4.5.2: "The output filter of the interface connected to
|
||||
* advertising or GATT bearers shall drop all messages with TTL value
|
||||
* set to 1." If a TTL=1 packet wasn't for a local interface, it is
|
||||
* invalid.
|
||||
*/
|
||||
if (tx->ctx->send_ttl == 1U) {
|
||||
err = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = bt_mesh_net_encrypt(enc, &buf->b, BT_MESH_NET_IVI_TX, false);
|
||||
if (err) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
err = bt_mesh_net_obfuscate(buf->data, BT_MESH_NET_IVI_TX, priv);
|
||||
if (err) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Deliver to GATT Proxy Clients if necessary. */
|
||||
if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY) &&
|
||||
bt_mesh_proxy_relay(&buf->b, tx->ctx->addr) &&
|
||||
BT_MESH_ADDR_IS_UNICAST(tx->ctx->addr)) {
|
||||
/* Notify completion if this only went through the Mesh Proxy */
|
||||
send_cb_finalize(cb, cb_data);
|
||||
|
||||
err = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
bt_mesh_adv_send(buf, cb, cb_data);
|
||||
|
||||
done:
|
||||
net_buf_unref(buf);
|
||||
return err;
|
||||
}
|
||||
|
||||
void bt_mesh_net_loopback_clear(uint16_t net_idx)
|
||||
{
|
||||
sys_slist_t new_list;
|
||||
struct net_buf *buf;
|
||||
|
||||
BT_DBG("0x%04x", net_idx);
|
||||
|
||||
sys_slist_init(&new_list);
|
||||
|
||||
while ((buf = net_buf_slist_get(&bt_mesh.local_queue))) {
|
||||
struct bt_mesh_subnet *sub = LOOPBACK_BUF_SUB(buf);
|
||||
|
||||
if (net_idx == BT_MESH_KEY_ANY || net_idx == sub->net_idx) {
|
||||
BT_DBG("Dropped 0x%06x", SEQ(buf->data));
|
||||
net_buf_unref(buf);
|
||||
} else {
|
||||
net_buf_slist_put(&new_list, buf);
|
||||
}
|
||||
}
|
||||
|
||||
bt_mesh.local_queue = new_list;
|
||||
}
|
||||
|
||||
static bool auth_match(struct bt_mesh_subnet_keys *keys,
|
||||
const uint8_t net_id[8], uint8_t flags,
|
||||
uint32_t iv_index, const uint8_t auth[8])
|
||||
|
@ -935,7 +1045,12 @@ static int net_decrypt(struct bt_mesh_subnet *sub, const uint8_t *enc,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (rx->net_if == BT_MESH_NET_IF_ADV && msg_cache_match(rx, buf)) {
|
||||
if (bt_mesh_elem_find(rx->ctx.addr)) {
|
||||
BT_DBG("Dropping locally originated packet");
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
if (rx->net_if == BT_MESH_NET_IF_ADV && msg_cache_match(buf)) {
|
||||
BT_DBG("Duplicate found in Network Message Cache");
|
||||
return -EALREADY;
|
||||
}
|
||||
|
@ -1044,8 +1159,6 @@ static bool net_find_and_decrypt(const uint8_t *data, size_t data_len,
|
|||
static bool relay_to_adv(enum bt_mesh_net_if net_if)
|
||||
{
|
||||
switch (net_if) {
|
||||
case BT_MESH_NET_IF_LOCAL:
|
||||
return true;
|
||||
case BT_MESH_NET_IF_ADV:
|
||||
return (bt_mesh_relay_get() == BT_MESH_RELAY_ENABLED);
|
||||
case BT_MESH_NET_IF_PROXY:
|
||||
|
@ -1062,20 +1175,8 @@ static void bt_mesh_net_relay(struct net_buf_simple *sbuf,
|
|||
struct net_buf *buf;
|
||||
uint8_t nid, transmit;
|
||||
|
||||
if (rx->net_if == BT_MESH_NET_IF_LOCAL) {
|
||||
/* Locally originated PDUs with TTL=1 will only be delivered
|
||||
* to local elements as per Mesh Profile 1.0 section 3.4.5.2:
|
||||
* "The output filter of the interface connected to
|
||||
* advertising or GATT bearers shall drop all messages with
|
||||
* TTL value set to 1."
|
||||
*/
|
||||
if (rx->ctx.recv_ttl == 1U) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (rx->ctx.recv_ttl <= 1U) {
|
||||
return;
|
||||
}
|
||||
if (rx->ctx.recv_ttl <= 1U) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rx->net_if == BT_MESH_NET_IF_ADV &&
|
||||
|
@ -1103,12 +1204,9 @@ static void bt_mesh_net_relay(struct net_buf_simple *sbuf,
|
|||
return;
|
||||
}
|
||||
|
||||
/* Only decrement TTL for non-locally originated packets */
|
||||
if (rx->net_if != BT_MESH_NET_IF_LOCAL) {
|
||||
/* Leave CTL bit intact */
|
||||
sbuf->data[1] &= 0x80;
|
||||
sbuf->data[1] |= rx->ctx.recv_ttl - 1U;
|
||||
}
|
||||
/* Leave CTL bit intact */
|
||||
sbuf->data[1] &= 0x80;
|
||||
sbuf->data[1] |= rx->ctx.recv_ttl - 1U;
|
||||
|
||||
net_buf_add_mem(buf, sbuf->data, sbuf->len);
|
||||
|
||||
|
@ -1139,11 +1237,10 @@ static void bt_mesh_net_relay(struct net_buf_simple *sbuf,
|
|||
}
|
||||
|
||||
/* Sending to the GATT bearer should only happen if GATT Proxy
|
||||
* is enabled or the message originates from the local node.
|
||||
* is enabled.
|
||||
*/
|
||||
if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY) &&
|
||||
(bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED ||
|
||||
rx->net_if == BT_MESH_NET_IF_LOCAL)) {
|
||||
bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED) {
|
||||
if (bt_mesh_proxy_relay(&buf->b, rx->ctx.recv_dst) &&
|
||||
BT_MESH_ADDR_IS_UNICAST(rx->ctx.recv_dst)) {
|
||||
goto done;
|
||||
|
@ -1215,11 +1312,6 @@ int bt_mesh_net_decode(struct net_buf_simple *data, enum bt_mesh_net_if net_if,
|
|||
return -EBADMSG;
|
||||
}
|
||||
|
||||
if (net_if != BT_MESH_NET_IF_LOCAL && bt_mesh_elem_find(rx->ctx.addr)) {
|
||||
BT_DBG("Dropping locally originated packet");
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
BT_DBG("src 0x%04x dst 0x%04x ttl %u", rx->ctx.addr, rx->ctx.recv_dst,
|
||||
rx->ctx.recv_ttl);
|
||||
BT_DBG("PDU: %s", bt_hex(buf->data, buf->len));
|
||||
|
|
|
@ -342,6 +342,8 @@ int bt_mesh_net_decode(struct net_buf_simple *data, enum bt_mesh_net_if net_if,
|
|||
void bt_mesh_net_recv(struct net_buf_simple *data, int8_t rssi,
|
||||
enum bt_mesh_net_if net_if);
|
||||
|
||||
void bt_mesh_net_loopback_clear(uint16_t net_idx);
|
||||
|
||||
uint32_t bt_mesh_next_seq(void);
|
||||
|
||||
void bt_mesh_net_start(void);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue