zephyr/subsys/net/l2/openthread/openthread.c
Robert Lubos ee1271f23c net: openthread: Disable ND/MLD for OpenThread interfaces
OpenThread networks do not use ND or MLD as they have their own set of
protocols to cover this functionality. So far, the use of OpenThread
with Zephyr relied on disabling those protocols support statically with
Kconfig, which prevented OT interfaces from being used along with other
IPv6 interfaces.

With the new network interface flags, it's now possible to disable ND
and MLD on individual interfaces, therefore disable them for OT
specifically.

Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
2023-04-29 12:24:49 +02:00

687 lines
16 KiB
C

/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_l2_openthread, CONFIG_OPENTHREAD_L2_LOG_LEVEL);
#include <zephyr/net/net_core.h>
#include <zephyr/net/net_pkt.h>
#include <zephyr/net/net_mgmt.h>
#include <zephyr/net/openthread.h>
#include <net_private.h>
#include <zephyr/init.h>
#include <zephyr/sys/check.h>
#include <zephyr/sys/slist.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/__assert.h>
#include <version.h>
#include <openthread/cli.h>
#include <openthread/ip6.h>
#include <openthread/link.h>
#include <openthread/link_raw.h>
#include <openthread/ncp.h>
#include <openthread/message.h>
#include <openthread/platform/diag.h>
#include <openthread/tasklet.h>
#include <openthread/thread.h>
#include <openthread/dataset.h>
#include <openthread/joiner.h>
#include <openthread-system.h>
#include <utils/uart.h>
#include <platform-zephyr.h>
#include "openthread_utils.h"
#define OT_STACK_SIZE (CONFIG_OPENTHREAD_THREAD_STACK_SIZE)
#if defined(CONFIG_OPENTHREAD_THREAD_PREEMPTIVE)
#define OT_PRIORITY K_PRIO_PREEMPT(CONFIG_OPENTHREAD_THREAD_PRIORITY)
#else
#define OT_PRIORITY K_PRIO_COOP(CONFIG_OPENTHREAD_THREAD_PRIORITY)
#endif
#if defined(CONFIG_OPENTHREAD_NETWORK_NAME)
#define OT_NETWORK_NAME CONFIG_OPENTHREAD_NETWORK_NAME
#else
#define OT_NETWORK_NAME ""
#endif
#if defined(CONFIG_OPENTHREAD_CHANNEL)
#define OT_CHANNEL CONFIG_OPENTHREAD_CHANNEL
#else
#define OT_CHANNEL 0
#endif
#if defined(CONFIG_OPENTHREAD_PANID)
#define OT_PANID CONFIG_OPENTHREAD_PANID
#else
#define OT_PANID 0
#endif
#if defined(CONFIG_OPENTHREAD_XPANID)
#define OT_XPANID CONFIG_OPENTHREAD_XPANID
#else
#define OT_XPANID ""
#endif
#if defined(CONFIG_OPENTHREAD_NETWORKKEY)
#define OT_NETWORKKEY CONFIG_OPENTHREAD_NETWORKKEY
#else
#define OT_NETWORKKEY ""
#endif
#if defined(CONFIG_OPENTHREAD_JOINER_PSKD)
#define OT_JOINER_PSKD CONFIG_OPENTHREAD_JOINER_PSKD
#else
#define OT_JOINER_PSKD ""
#endif
#if defined(CONFIG_OPENTHREAD_PLATFORM_INFO)
#define OT_PLATFORM_INFO CONFIG_OPENTHREAD_PLATFORM_INFO
#else
#define OT_PLATFORM_INFO ""
#endif
#if defined(CONFIG_OPENTHREAD_POLL_PERIOD)
#define OT_POLL_PERIOD CONFIG_OPENTHREAD_POLL_PERIOD
#else
#define OT_POLL_PERIOD 0
#endif
#define PACKAGE_NAME "Zephyr"
#define PACKAGE_VERSION KERNEL_VERSION_STRING
extern void platformShellInit(otInstance *aInstance);
K_KERNEL_STACK_DEFINE(ot_stack_area, OT_STACK_SIZE);
static struct net_linkaddr *ll_addr;
static otStateChangedCallback state_changed_cb;
k_tid_t openthread_thread_id_get(void)
{
struct openthread_context *ot_context = openthread_get_default_context();
return ot_context ? (k_tid_t)&ot_context->work_q.thread : 0;
}
#ifdef CONFIG_NET_MGMT_EVENT
static struct net_mgmt_event_callback ip6_addr_cb;
static void ipv6_addr_event_handler(struct net_mgmt_event_callback *cb,
uint32_t mgmt_event, struct net_if *iface)
{
if (net_if_l2(iface) != &NET_L2_GET_NAME(OPENTHREAD)) {
return;
}
#ifdef CONFIG_NET_MGMT_EVENT_INFO
struct openthread_context *ot_context = net_if_l2_data(iface);
if (cb->info == NULL || cb->info_length != sizeof(struct in6_addr)) {
return;
}
if (mgmt_event == NET_EVENT_IPV6_ADDR_ADD) {
add_ipv6_addr_to_ot(ot_context, (const struct in6_addr *)cb->info);
} else if (mgmt_event == NET_EVENT_IPV6_MADDR_ADD) {
add_ipv6_maddr_to_ot(ot_context, (const struct in6_addr *)cb->info);
}
#else
NET_WARN("No address info provided with event, "
"please enable CONFIG_NET_MGMT_EVENT_INFO");
#endif /* CONFIG_NET_MGMT_EVENT_INFO */
}
#endif /* CONFIG_NET_MGMT_EVENT */
static int ncp_hdlc_send(const uint8_t *buf, uint16_t len)
{
otError err;
err = otPlatUartSend(buf, len);
if (err != OT_ERROR_NONE) {
return 0;
}
return len;
}
void otPlatRadioGetIeeeEui64(otInstance *instance, uint8_t *ieee_eui64)
{
ARG_UNUSED(instance);
memcpy(ieee_eui64, ll_addr->addr, ll_addr->len);
}
void otTaskletsSignalPending(otInstance *instance)
{
struct openthread_context *ot_context = openthread_get_default_context();
if (ot_context) {
k_work_submit_to_queue(&ot_context->work_q, &ot_context->api_work);
}
}
void otSysEventSignalPending(void)
{
otTaskletsSignalPending(NULL);
}
static void ot_state_changed_handler(uint32_t flags, void *context)
{
struct openthread_state_changed_cb *entry, *next;
struct openthread_context *ot_context = context;
NET_INFO("State changed! Flags: 0x%08" PRIx32 " Current role: %s",
flags,
otThreadDeviceRoleToString(otThreadGetDeviceRole(ot_context->instance))
);
if (flags & OT_CHANGED_THREAD_ROLE) {
switch (otThreadGetDeviceRole(ot_context->instance)) {
case OT_DEVICE_ROLE_CHILD:
case OT_DEVICE_ROLE_ROUTER:
case OT_DEVICE_ROLE_LEADER:
net_if_dormant_off(ot_context->iface);
break;
case OT_DEVICE_ROLE_DISABLED:
case OT_DEVICE_ROLE_DETACHED:
default:
net_if_dormant_on(ot_context->iface);
break;
}
}
if (flags & OT_CHANGED_IP6_ADDRESS_REMOVED) {
NET_DBG("Ipv6 address removed");
rm_ipv6_addr_from_zephyr(ot_context);
}
if (flags & OT_CHANGED_IP6_ADDRESS_ADDED) {
NET_DBG("Ipv6 address added");
add_ipv6_addr_to_zephyr(ot_context);
}
if (flags & OT_CHANGED_IP6_MULTICAST_UNSUBSCRIBED) {
NET_DBG("Ipv6 multicast address removed");
rm_ipv6_maddr_from_zephyr(ot_context);
}
if (flags & OT_CHANGED_IP6_MULTICAST_SUBSCRIBED) {
NET_DBG("Ipv6 multicast address added");
add_ipv6_maddr_to_zephyr(ot_context);
}
if (state_changed_cb) {
state_changed_cb(flags, context);
}
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&ot_context->state_change_cbs, entry, next, node) {
if (entry->state_changed_cb != NULL) {
entry->state_changed_cb(flags, ot_context, entry->user_data);
}
}
}
static void ot_receive_handler(otMessage *aMessage, void *context)
{
struct openthread_context *ot_context = context;
uint16_t offset = 0U;
uint16_t read_len;
struct net_pkt *pkt;
struct net_buf *pkt_buf;
pkt = net_pkt_rx_alloc_with_buffer(ot_context->iface,
otMessageGetLength(aMessage),
AF_UNSPEC, 0, K_NO_WAIT);
if (!pkt) {
NET_ERR("Failed to reserve net pkt");
goto out;
}
pkt_buf = pkt->buffer;
while (1) {
read_len = otMessageRead(aMessage, offset, pkt_buf->data,
net_buf_tailroom(pkt_buf));
if (!read_len) {
break;
}
net_buf_add(pkt_buf, read_len);
offset += read_len;
if (!net_buf_tailroom(pkt_buf)) {
pkt_buf = pkt_buf->frags;
if (!pkt_buf) {
break;
}
}
}
NET_DBG("Injecting Ip6 packet to Zephyr net stack");
if (IS_ENABLED(CONFIG_OPENTHREAD_L2_DEBUG_DUMP_IPV6)) {
net_pkt_hexdump(pkt, "Received IPv6 packet");
}
if (!pkt_list_is_full(ot_context)) {
if (pkt_list_add(ot_context, pkt) != 0) {
NET_ERR("pkt_list_add failed");
goto out;
}
if (net_recv_data(ot_context->iface, pkt) < 0) {
NET_ERR("net_recv_data failed");
pkt_list_remove_first(ot_context);
goto out;
}
pkt = NULL;
} else {
NET_INFO("Packet list is full");
}
out:
if (pkt) {
net_pkt_unref(pkt);
}
otMessageFree(aMessage);
}
static void ot_joiner_start_handler(otError error, void *context)
{
struct openthread_context *ot_context = context;
switch (error) {
case OT_ERROR_NONE:
NET_INFO("Join success");
otThreadSetEnabled(ot_context->instance, true);
break;
default:
NET_ERR("Join failed [%d]", error);
break;
}
}
static void openthread_process(struct k_work *work)
{
struct openthread_context *ot_context
= CONTAINER_OF(work, struct openthread_context, api_work);
openthread_api_mutex_lock(ot_context);
while (otTaskletsArePending(ot_context->instance)) {
otTaskletsProcess(ot_context->instance);
}
otSysProcessDrivers(ot_context->instance);
openthread_api_mutex_unlock(ot_context);
}
static bool is_ipv6_frag(struct net_pkt *pkt)
{
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv6_access, struct net_ipv6_hdr);
struct net_ipv6_hdr *hdr;
hdr = (struct net_ipv6_hdr *)net_pkt_get_data(pkt, &ipv6_access);
if (!hdr) {
return false;
}
return hdr->nexthdr == NET_IPV6_NEXTHDR_FRAG ? true : false;
}
static enum net_verdict openthread_recv(struct net_if *iface, struct net_pkt *pkt)
{
struct openthread_context *ot_context = net_if_l2_data(iface);
if (pkt_list_peek(ot_context) == pkt) {
pkt_list_remove_last(ot_context);
NET_DBG("Got injected Ip6 packet, sending to upper layers");
if (IS_ENABLED(CONFIG_OPENTHREAD_L2_DEBUG_DUMP_IPV6)) {
net_pkt_hexdump(pkt, "Injected IPv6 packet");
}
if (IS_ENABLED(CONFIG_OPENTHREAD_IP6_FRAGM) && is_ipv6_frag(pkt)) {
return NET_DROP;
}
return NET_CONTINUE;
}
NET_DBG("Got 802.15.4 packet, sending to OT");
if (IS_ENABLED(CONFIG_OPENTHREAD_L2_DEBUG_DUMP_IPV6)) {
net_pkt_hexdump(pkt, "Received 802.15.4 frame");
}
if (notify_new_rx_frame(pkt) != 0) {
NET_ERR("Failed to queue RX packet for OpenThread");
return NET_DROP;
}
return NET_OK;
}
int openthread_send(struct net_if *iface, struct net_pkt *pkt)
{
int len = net_pkt_get_len(pkt);
if (IS_ENABLED(CONFIG_OPENTHREAD_L2_DEBUG_DUMP_IPV6)) {
net_pkt_hexdump(pkt, "IPv6 packet to send");
}
net_capture_pkt(iface, pkt);
if (notify_new_tx_frame(pkt) != 0) {
net_pkt_unref(pkt);
}
return len;
}
int openthread_start(struct openthread_context *ot_context)
{
otInstance *ot_instance = ot_context->instance;
otError error = OT_ERROR_NONE;
openthread_api_mutex_lock(ot_context);
NET_INFO("OpenThread version: %s", otGetVersionString());
if (IS_ENABLED(CONFIG_OPENTHREAD_COPROCESSOR)) {
NET_DBG("OpenThread co-processor.");
goto exit;
}
otIp6SetEnabled(ot_context->instance, true);
/* Sleepy End Device specific configuration. */
if (IS_ENABLED(CONFIG_OPENTHREAD_MTD_SED)) {
otLinkModeConfig ot_mode = otThreadGetLinkMode(ot_instance);
/* A SED should always attach the network as a SED to indicate
* increased buffer requirement to a parent.
*/
ot_mode.mRxOnWhenIdle = false;
otThreadSetLinkMode(ot_context->instance, ot_mode);
otLinkSetPollPeriod(ot_context->instance, OT_POLL_PERIOD);
}
if (otDatasetIsCommissioned(ot_instance)) {
/* OpenThread already has dataset stored - skip the
* configuration.
*/
NET_DBG("OpenThread already commissioned.");
} else if (IS_ENABLED(CONFIG_OPENTHREAD_JOINER_AUTOSTART)) {
/* No dataset - initiate network join procedure. */
NET_DBG("Starting OpenThread join procedure.");
error = otJoinerStart(ot_instance, OT_JOINER_PSKD, NULL,
PACKAGE_NAME, OT_PLATFORM_INFO,
PACKAGE_VERSION, NULL,
&ot_joiner_start_handler, ot_context);
if (error != OT_ERROR_NONE) {
NET_ERR("Failed to start joiner [%d]", error);
}
goto exit;
} else {
/* No dataset - load the default configuration. */
NET_DBG("Loading OpenThread default configuration.");
otExtendedPanId xpanid;
otNetworkKey networkKey;
otThreadSetNetworkName(ot_instance, OT_NETWORK_NAME);
otLinkSetChannel(ot_instance, OT_CHANNEL);
otLinkSetPanId(ot_instance, OT_PANID);
net_bytes_from_str(xpanid.m8, 8, (char *)OT_XPANID);
otThreadSetExtendedPanId(ot_instance, &xpanid);
if (strlen(OT_NETWORKKEY)) {
net_bytes_from_str(networkKey.m8, OT_NETWORK_KEY_SIZE,
(char *)OT_NETWORKKEY);
otThreadSetNetworkKey(ot_instance, &networkKey);
}
}
NET_INFO("Network name: %s",
otThreadGetNetworkName(ot_instance));
/* Start the network. */
error = otThreadSetEnabled(ot_instance, true);
if (error != OT_ERROR_NONE) {
NET_ERR("Failed to start the OpenThread network [%d]", error);
}
exit:
openthread_api_mutex_unlock(ot_context);
return error == OT_ERROR_NONE ? 0 : -EIO;
}
int openthread_stop(struct openthread_context *ot_context)
{
otError error;
if (IS_ENABLED(CONFIG_OPENTHREAD_COPROCESSOR)) {
return 0;
}
openthread_api_mutex_lock(ot_context);
error = otThreadSetEnabled(ot_context->instance, false);
if (error == OT_ERROR_INVALID_STATE) {
NET_DBG("Openthread interface was not up [%d]", error);
}
openthread_api_mutex_unlock(ot_context);
return 0;
}
static int openthread_init(struct net_if *iface)
{
struct openthread_context *ot_context = net_if_l2_data(iface);
struct k_work_queue_config q_cfg = {
.name = "openthread",
.no_yield = true,
};
otError err;
NET_DBG("openthread_init");
k_mutex_init(&ot_context->api_lock);
k_work_init(&ot_context->api_work, openthread_process);
ll_addr = net_if_get_link_addr(iface);
openthread_api_mutex_lock(ot_context);
otSysInit(0, NULL);
ot_context->instance = otInstanceInitSingle();
ot_context->iface = iface;
__ASSERT(ot_context->instance, "OT instance is NULL");
if (IS_ENABLED(CONFIG_OPENTHREAD_SHELL)) {
platformShellInit(ot_context->instance);
}
if (IS_ENABLED(CONFIG_OPENTHREAD_COPROCESSOR)) {
otPlatUartEnable();
otNcpHdlcInit(ot_context->instance, ncp_hdlc_send);
} else {
otIp6SetReceiveFilterEnabled(ot_context->instance, true);
otIp6SetReceiveCallback(ot_context->instance,
ot_receive_handler, ot_context);
sys_slist_init(&ot_context->state_change_cbs);
err = otSetStateChangedCallback(ot_context->instance,
&ot_state_changed_handler,
ot_context);
if (err != OT_ERROR_NONE) {
NET_ERR("Could not set state changed callback: %d", err);
}
net_mgmt_init_event_callback(
&ip6_addr_cb, ipv6_addr_event_handler,
NET_EVENT_IPV6_ADDR_ADD | NET_EVENT_IPV6_MADDR_ADD);
net_mgmt_add_event_callback(&ip6_addr_cb);
net_if_dormant_on(iface);
}
openthread_api_mutex_unlock(ot_context);
k_work_queue_start(&ot_context->work_q, ot_stack_area,
K_KERNEL_STACK_SIZEOF(ot_stack_area),
OT_PRIORITY, &q_cfg);
(void)k_work_submit_to_queue(&ot_context->work_q, &ot_context->api_work);
return 0;
}
void ieee802154_init(struct net_if *iface)
{
if (IS_ENABLED(CONFIG_IEEE802154_NET_IF_NO_AUTO_START)) {
LOG_DBG("Interface auto start disabled.");
net_if_flag_set(iface, NET_IF_NO_AUTO_START);
}
net_if_flag_set(iface, NET_IF_IPV6_NO_ND);
net_if_flag_set(iface, NET_IF_IPV6_NO_MLD);
openthread_init(iface);
}
static enum net_l2_flags openthread_flags(struct net_if *iface)
{
/* TODO: Should report NET_L2_PROMISC_MODE if the radio driver
* reports the IEEE802154_HW_PROMISC capability.
*/
return NET_L2_MULTICAST | NET_L2_MULTICAST_SKIP_JOIN_SOLICIT_NODE;
}
static int openthread_enable(struct net_if *iface, bool state)
{
struct openthread_context *ot_context = net_if_l2_data(iface);
NET_DBG("iface %p %s", iface, state ? "up" : "down");
if (state) {
if (IS_ENABLED(CONFIG_OPENTHREAD_MANUAL_START)) {
NET_DBG("OpenThread manual start.");
return 0;
}
return openthread_start(ot_context);
}
return openthread_stop(ot_context);
}
struct openthread_context *openthread_get_default_context(void)
{
struct net_if *iface;
struct openthread_context *ot_context = NULL;
iface = net_if_get_first_by_type(&NET_L2_GET_NAME(OPENTHREAD));
if (!iface) {
NET_ERR("There is no net interface for OpenThread");
goto exit;
}
ot_context = net_if_l2_data(iface);
if (!ot_context) {
NET_ERR("There is no Openthread context in net interface data");
goto exit;
}
exit:
return ot_context;
}
struct otInstance *openthread_get_default_instance(void)
{
struct openthread_context *ot_context =
openthread_get_default_context();
return ot_context ? ot_context->instance : NULL;
}
int openthread_state_changed_cb_register(struct openthread_context *ot_context,
struct openthread_state_changed_cb *cb)
{
CHECKIF(cb == NULL || cb->state_changed_cb == NULL) {
return -EINVAL;
}
openthread_api_mutex_lock(ot_context);
sys_slist_append(&ot_context->state_change_cbs, &cb->node);
openthread_api_mutex_unlock(ot_context);
return 0;
}
int openthread_state_changed_cb_unregister(struct openthread_context *ot_context,
struct openthread_state_changed_cb *cb)
{
bool removed;
CHECKIF(cb == NULL) {
return -EINVAL;
}
openthread_api_mutex_lock(ot_context);
removed = sys_slist_find_and_remove(&ot_context->state_change_cbs, &cb->node);
openthread_api_mutex_unlock(ot_context);
if (!removed) {
return -EALREADY;
}
return 0;
}
void openthread_set_state_changed_cb(otStateChangedCallback cb)
{
state_changed_cb = cb;
}
void openthread_api_mutex_lock(struct openthread_context *ot_context)
{
(void)k_mutex_lock(&ot_context->api_lock, K_FOREVER);
}
int openthread_api_mutex_try_lock(struct openthread_context *ot_context)
{
return k_mutex_lock(&ot_context->api_lock, K_NO_WAIT);
}
void openthread_api_mutex_unlock(struct openthread_context *ot_context)
{
(void)k_mutex_unlock(&ot_context->api_lock);
}
NET_L2_INIT(OPENTHREAD_L2, openthread_recv, openthread_send, openthread_enable,
openthread_flags);