blob: 09097f49b00404ea4100a970011f95afce9fc8c2 [file] [log] [blame]
/*
* Copyright (c) 2019 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(conn_mgr, CONFIG_NET_CONNECTION_MANAGER_LOG_LEVEL);
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <errno.h>
#include <zephyr/net/net_core.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_mgmt.h>
#include <zephyr/sys/iterable_sections.h>
#include <zephyr/net/conn_mgr_connectivity.h>
#include "conn_mgr_private.h"
#if defined(CONFIG_NET_TC_THREAD_COOPERATIVE)
#define THREAD_PRIORITY K_PRIO_COOP(CONFIG_NUM_COOP_PRIORITIES - 1)
#else
#define THREAD_PRIORITY K_PRIO_PREEMPT(7)
#endif
static K_THREAD_STACK_DEFINE(conn_mgr_mon_stack,
CONFIG_NET_CONNECTION_MANAGER_MONITOR_STACK_SIZE);
static struct k_thread conn_mgr_mon_thread;
/* Internal state array tracking readiness, flags, and other state information for all available
* ifaces. Note that indexing starts at 0, whereas Zephyr iface indices start at 1.
* conn_mgr_mon_get_if_by_index and conn_mgr_get_index_for_if are used to go back and forth between
* iface_states indices and Zephyr iface pointers.
*/
uint16_t iface_states[CONN_MGR_IFACE_MAX];
/* Tracks the most recent total quantity of L4-ready ifaces (any, IPv4, IPv6) */
static uint16_t last_ready_count;
static uint16_t last_ready_count_ipv4;
static uint16_t last_ready_count_ipv6;
/* Tracks the last ifaces to cause a major state change (any, IPv4, IPv6) */
static struct net_if *last_blame;
static struct net_if *last_blame_ipv4;
static struct net_if *last_blame_ipv6;
/* Used to signal when modifications have been made that need to be responded to */
K_SEM_DEFINE(conn_mgr_mon_updated, 1, 1);
/* Used to protect conn_mgr_monitor state */
K_MUTEX_DEFINE(conn_mgr_mon_lock);
/**
* @brief Retrieves pointer to an iface by the index that corresponds to it in iface_states
*
* @param index - The index in iface_states to find the corresponding iface for.
* @return net_if* - The corresponding iface.
*/
static struct net_if *conn_mgr_mon_get_if_by_index(int index)
{
return net_if_get_by_index(index + 1);
}
/**
* @brief Gets the index in iface_states for the state corresponding to a provided iface.
*
* @param iface - iface to find the index of.
* @return int - The index found.
*/
static int conn_mgr_get_index_for_if(struct net_if *iface)
{
return net_if_get_by_iface(iface) - 1;
}
/**
* @brief Conveniently update iface readiness state
*
* @param idx - index (in iface_states) of the iface to mark ready or unready
* @param ready - true if the iface should be considered ready, otherwise false
* @param ready_ipv4 - true if the iface is ready with IPv4, otherwise false
* @param ready_ipv6 - true if the iface is ready with IPv6, otherwise false
*/
static void conn_mgr_mon_set_ready(int idx, bool ready, bool ready_ipv4, bool ready_ipv6)
{
/* Clear and then update the L4-readiness bit */
iface_states[idx] &= ~CONN_MGR_IF_READY;
iface_states[idx] &= ~CONN_MGR_IF_READY_IPV4;
iface_states[idx] &= ~CONN_MGR_IF_READY_IPV6;
if (ready) {
iface_states[idx] |= CONN_MGR_IF_READY;
}
if (ready_ipv4) {
iface_states[idx] |= CONN_MGR_IF_READY_IPV4;
}
if (ready_ipv6) {
iface_states[idx] |= CONN_MGR_IF_READY_IPV6;
}
}
static void conn_mgr_mon_handle_update(void)
{
int idx;
bool has_ip;
bool has_ipv6;
bool has_ipv4;
bool is_l4_ready;
bool is_ipv6_ready;
bool is_ipv4_ready;
bool is_oper_up;
bool was_l4_ready;
bool was_ipv6_ready;
bool was_ipv4_ready;
bool is_ignored;
int ready_count = 0;
int ready_count_ipv4 = 0;
int ready_count_ipv6 = 0;
struct net_if *blame = NULL;
struct net_if *blame_ipv4 = NULL;
struct net_if *blame_ipv6 = NULL;
k_mutex_lock(&conn_mgr_mon_lock, K_FOREVER);
for (idx = 0; idx < ARRAY_SIZE(iface_states); idx++) {
if (iface_states[idx] == 0) {
/* This interface is not used */
continue;
}
/* Detect whether iface was previously considered ready */
was_l4_ready = iface_states[idx] & CONN_MGR_IF_READY;
was_ipv6_ready = iface_states[idx] & CONN_MGR_IF_READY_IPV6;
was_ipv4_ready = iface_states[idx] & CONN_MGR_IF_READY_IPV4;
/* Collect iface readiness requirements */
has_ipv6 = iface_states[idx] & CONN_MGR_IF_IPV6_SET;
has_ipv4 = iface_states[idx] & CONN_MGR_IF_IPV4_SET;
has_ip = has_ipv6 || has_ipv4;
is_oper_up = iface_states[idx] & CONN_MGR_IF_UP;
is_ignored = iface_states[idx] & CONN_MGR_IF_IGNORED;
/* Determine whether iface is currently considered ready */
is_l4_ready = is_oper_up && has_ip && !is_ignored;
is_ipv6_ready = is_oper_up && has_ipv6 && !is_ignored;
is_ipv4_ready = is_oper_up && has_ipv4 && !is_ignored;
/* Track ready iface count */
if (is_l4_ready) {
ready_count += 1;
}
if (is_ipv6_ready) {
ready_count_ipv6 += 1;
}
if (is_ipv4_ready) {
ready_count_ipv4 += 1;
}
/* If any states changed, track blame for possibly triggered events */
if (was_l4_ready != is_l4_ready) {
blame = conn_mgr_mon_get_if_by_index(idx);
}
if (was_ipv6_ready != is_ipv6_ready) {
blame_ipv6 = conn_mgr_mon_get_if_by_index(idx);
}
if (was_ipv4_ready != is_ipv4_ready) {
blame_ipv4 = conn_mgr_mon_get_if_by_index(idx);
}
/* Update readiness state flags with the (possibly) new values */
conn_mgr_mon_set_ready(idx, is_l4_ready, is_ipv4_ready, is_ipv6_ready);
}
/* If the total number of ready ifaces changed, possibly send an event */
if (ready_count != last_ready_count) {
if (ready_count == 0) {
/* We just lost connectivity */
net_mgmt_event_notify(NET_EVENT_L4_DISCONNECTED, blame);
} else if (last_ready_count == 0) {
/* We just gained connectivity */
net_mgmt_event_notify(NET_EVENT_L4_CONNECTED, blame);
}
last_ready_count = ready_count;
last_blame = blame;
}
/* Same, but specifically for IPv4 */
if (ready_count_ipv4 != last_ready_count_ipv4) {
if (ready_count_ipv4 == 0) {
/* We just lost IPv4 connectivity */
net_mgmt_event_notify(NET_EVENT_L4_IPV4_DISCONNECTED, blame_ipv4);
} else if (last_ready_count_ipv4 == 0) {
/* We just gained IPv4 connectivity */
net_mgmt_event_notify(NET_EVENT_L4_IPV4_CONNECTED, blame_ipv4);
}
last_ready_count_ipv4 = ready_count_ipv4;
last_blame_ipv4 = blame_ipv4;
}
/* Same, but specifically for IPv6 */
if (ready_count_ipv6 != last_ready_count_ipv6) {
if (ready_count_ipv6 == 0) {
/* We just lost IPv6 connectivity */
net_mgmt_event_notify(NET_EVENT_L4_IPV6_DISCONNECTED, blame_ipv6);
} else if (last_ready_count_ipv6 == 0) {
/* We just gained IPv6 connectivity */
net_mgmt_event_notify(NET_EVENT_L4_IPV6_CONNECTED, blame_ipv6);
}
last_ready_count_ipv6 = ready_count_ipv6;
last_blame_ipv6 = blame_ipv6;
}
k_mutex_unlock(&conn_mgr_mon_lock);
}
/**
* @brief Initialize the internal state flags for the given iface using its current status
*
* @param iface - iface to initialize from.
*/
static void conn_mgr_mon_initial_state(struct net_if *iface)
{
int idx = net_if_get_by_iface(iface) - 1;
k_mutex_lock(&conn_mgr_mon_lock, K_FOREVER);
if (net_if_is_up(iface)) {
NET_DBG("Iface %p UP", iface);
iface_states[idx] |= CONN_MGR_IF_UP;
}
if (IS_ENABLED(CONFIG_NET_NATIVE_IPV6)) {
if (net_if_ipv6_get_global_addr(NET_ADDR_PREFERRED, &iface)) {
NET_DBG("IPv6 addr set");
iface_states[idx] |= CONN_MGR_IF_IPV6_SET;
}
}
if (IS_ENABLED(CONFIG_NET_NATIVE_IPV4)) {
if (net_if_ipv4_get_global_addr(iface, NET_ADDR_PREFERRED)) {
NET_DBG("IPv4 addr set");
iface_states[idx] |= CONN_MGR_IF_IPV4_SET;
}
}
k_mutex_unlock(&conn_mgr_mon_lock);
}
static void conn_mgr_mon_init_cb(struct net_if *iface, void *user_data)
{
ARG_UNUSED(user_data);
conn_mgr_mon_initial_state(iface);
}
static void conn_mgr_mon_thread_fn(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
k_mutex_lock(&conn_mgr_mon_lock, K_FOREVER);
conn_mgr_conn_init();
conn_mgr_init_events_handler();
net_if_foreach(conn_mgr_mon_init_cb, NULL);
k_mutex_unlock(&conn_mgr_mon_lock);
NET_DBG("Connection Manager started");
while (true) {
/* Wait for changes */
k_sem_take(&conn_mgr_mon_updated, K_FOREVER);
/* Respond to changes */
conn_mgr_mon_handle_update();
}
}
void conn_mgr_mon_resend_status(void)
{
k_mutex_lock(&conn_mgr_mon_lock, K_FOREVER);
if (last_ready_count == 0) {
net_mgmt_event_notify(NET_EVENT_L4_DISCONNECTED, last_blame);
} else {
net_mgmt_event_notify(NET_EVENT_L4_CONNECTED, last_blame);
}
if (last_ready_count_ipv6 == 0) {
net_mgmt_event_notify(NET_EVENT_L4_IPV6_DISCONNECTED, last_blame_ipv6);
} else {
net_mgmt_event_notify(NET_EVENT_L4_IPV6_CONNECTED, last_blame_ipv6);
}
if (last_ready_count_ipv4 == 0) {
net_mgmt_event_notify(NET_EVENT_L4_IPV4_DISCONNECTED, last_blame_ipv4);
} else {
net_mgmt_event_notify(NET_EVENT_L4_IPV4_CONNECTED, last_blame_ipv4);
}
k_mutex_unlock(&conn_mgr_mon_lock);
}
void conn_mgr_ignore_iface(struct net_if *iface)
{
int idx = conn_mgr_get_index_for_if(iface);
k_mutex_lock(&conn_mgr_mon_lock, K_FOREVER);
if (!(iface_states[idx] & CONN_MGR_IF_IGNORED)) {
/* Set ignored flag and mark state as changed */
iface_states[idx] |= CONN_MGR_IF_IGNORED;
k_sem_give(&conn_mgr_mon_updated);
}
k_mutex_unlock(&conn_mgr_mon_lock);
}
void conn_mgr_watch_iface(struct net_if *iface)
{
int idx = conn_mgr_get_index_for_if(iface);
k_mutex_lock(&conn_mgr_mon_lock, K_FOREVER);
if (iface_states[idx] & CONN_MGR_IF_IGNORED) {
/* Clear ignored flag and mark state as changed */
iface_states[idx] &= ~CONN_MGR_IF_IGNORED;
k_sem_give(&conn_mgr_mon_updated);
}
k_mutex_unlock(&conn_mgr_mon_lock);
}
bool conn_mgr_is_iface_ignored(struct net_if *iface)
{
int idx = conn_mgr_get_index_for_if(iface);
bool ret = false;
k_mutex_lock(&conn_mgr_mon_lock, K_FOREVER);
ret = iface_states[idx] & CONN_MGR_IF_IGNORED;
k_mutex_unlock(&conn_mgr_mon_lock);
return ret;
}
/**
* @brief Check whether a provided iface uses the provided L2.
*
* @param iface - iface to check.
* @param l2 - L2 to check. NULL will match offloaded ifaces.
* @retval true if the iface uses the provided L2.
* @retval false otherwise.
*/
static bool iface_uses_l2(struct net_if *iface, const struct net_l2 *l2)
{
return (!l2 && net_if_offload(iface)) ||
(net_if_l2(iface) == l2);
}
void conn_mgr_ignore_l2(const struct net_l2 *l2)
{
/* conn_mgr_ignore_iface already locks the mutex, but we lock it here too
* so that all matching ifaces are updated simultaneously.
*/
k_mutex_lock(&conn_mgr_mon_lock, K_FOREVER);
STRUCT_SECTION_FOREACH(net_if, iface) {
if (iface_uses_l2(iface, l2)) {
conn_mgr_ignore_iface(iface);
}
}
k_mutex_unlock(&conn_mgr_mon_lock);
}
void conn_mgr_watch_l2(const struct net_l2 *l2)
{
/* conn_mgr_watch_iface already locks the mutex, but we lock it here too
* so that all matching ifaces are updated simultaneously.
*/
k_mutex_lock(&conn_mgr_mon_lock, K_FOREVER);
STRUCT_SECTION_FOREACH(net_if, iface) {
if (iface_uses_l2(iface, l2)) {
conn_mgr_watch_iface(iface);
}
}
k_mutex_unlock(&conn_mgr_mon_lock);
}
static int conn_mgr_mon_init(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(iface_states); i++) {
iface_states[i] = 0;
}
k_thread_create(&conn_mgr_mon_thread, conn_mgr_mon_stack,
CONFIG_NET_CONNECTION_MANAGER_MONITOR_STACK_SIZE,
conn_mgr_mon_thread_fn,
NULL, NULL, NULL, THREAD_PRIORITY, 0, K_NO_WAIT);
k_thread_name_set(&conn_mgr_mon_thread, "conn_mgr_monitor");
return 0;
}
SYS_INIT(conn_mgr_mon_init, APPLICATION, CONFIG_NET_CONNECTION_MANAGER_MONITOR_PRIORITY);