blob: 4bc9fea34716ebf031c2feceacdc464a7a9dc828 [file] [log] [blame]
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(conn_mgr_conn, CONFIG_NET_CONNECTION_MANAGER_LOG_LEVEL);
#include <zephyr/net/net_if.h>
#include <zephyr/sys/iterable_sections.h>
#include <zephyr/net/conn_mgr_monitor.h>
#include <zephyr/net/conn_mgr_connectivity.h>
#include <zephyr/net/conn_mgr_connectivity_impl.h>
#include "conn_mgr_private.h"
int conn_mgr_if_connect(struct net_if *iface)
{
struct conn_mgr_conn_binding *binding;
struct conn_mgr_conn_api *api;
int status;
LOG_DBG("iface %p connect", iface);
binding = conn_mgr_if_get_binding(iface);
if (!binding) {
return -ENOTSUP;
}
api = binding->impl->api;
if (!api->connect) {
return -ENOTSUP;
}
conn_mgr_binding_lock(binding);
if (!net_if_is_admin_up(iface)) {
status = net_if_up(iface);
if (status) {
goto out;
}
}
status = api->connect(binding);
out:
conn_mgr_binding_unlock(binding);
return status;
}
static void conn_mgr_conn_if_auto_admin_down(struct net_if *iface);
int conn_mgr_if_disconnect(struct net_if *iface)
{
struct conn_mgr_conn_binding *binding;
struct conn_mgr_conn_api *api;
int status = 0;
LOG_DBG("iface %p disconnect", iface);
binding = conn_mgr_if_get_binding(iface);
if (!binding) {
return -ENOTSUP;
}
api = binding->impl->api;
if (!api->disconnect) {
return -ENOTSUP;
}
conn_mgr_binding_lock(binding);
if (!net_if_is_admin_up(iface)) {
goto out;
}
status = api->disconnect(binding);
out:
conn_mgr_binding_unlock(binding);
/* Since the connectivity implementation will not automatically attempt to reconnect after
* a call to conn_mgr_if_disconnect, conn_mgr_conn_if_auto_admin_down should be called.
*
* conn_mgr_conn_handle_iface_down will only call conn_mgr_conn_if_auto_admin_down if
* persistence is disabled. To ensure conn_mgr_conn_if_auto_admin_down is called in all
* cases, we must call it directly from here. If persistence is disabled, this will result
* in conn_mgr_conn_if_auto_admin_down being called twice, but that is not an issue.
*/
conn_mgr_conn_if_auto_admin_down(iface);
return status;
}
bool conn_mgr_if_is_bound(struct net_if *iface)
{
struct conn_mgr_conn_binding *binding = conn_mgr_if_get_binding(iface);
return binding != NULL;
}
int conn_mgr_if_get_opt(struct net_if *iface, int optname, void *optval, size_t *optlen)
{
struct conn_mgr_conn_binding *binding;
struct conn_mgr_conn_api *api;
int status;
if (!optlen) {
return -EINVAL;
}
if (!optval) {
*optlen = 0;
return -EINVAL;
}
binding = conn_mgr_if_get_binding(iface);
if (!binding) {
*optlen = 0;
return -ENOTSUP;
}
api = binding->impl->api;
if (!api->get_opt) {
*optlen = 0;
return -ENOTSUP;
}
conn_mgr_binding_lock(binding);
status = api->get_opt(binding, optname, optval, optlen);
conn_mgr_binding_unlock(binding);
return status;
}
int conn_mgr_if_set_opt(struct net_if *iface, int optname, const void *optval, size_t optlen)
{
struct conn_mgr_conn_binding *binding;
struct conn_mgr_conn_api *api;
int status;
if (!optval) {
return -EINVAL;
}
binding = conn_mgr_if_get_binding(iface);
if (!binding) {
return -ENOTSUP;
}
api = binding->impl->api;
if (!api->set_opt) {
return -ENOTSUP;
}
conn_mgr_binding_lock(binding);
status = api->set_opt(binding, optname, optval, optlen);
conn_mgr_binding_unlock(binding);
return status;
}
int conn_mgr_if_set_flag(struct net_if *iface, enum conn_mgr_if_flag flag, bool value)
{
struct conn_mgr_conn_binding *binding;
if (flag >= CONN_MGR_NUM_IF_FLAGS) {
return -EINVAL;
}
binding = conn_mgr_if_get_binding(iface);
if (!binding) {
return -ENOTSUP;
}
conn_mgr_binding_set_flag(binding, flag, value);
return 0;
}
bool conn_mgr_if_get_flag(struct net_if *iface, enum conn_mgr_if_flag flag)
{
struct conn_mgr_conn_binding *binding;
if (flag >= CONN_MGR_NUM_IF_FLAGS) {
return false;
}
binding = conn_mgr_if_get_binding(iface);
if (!binding) {
return false;
}
return conn_mgr_binding_get_flag(binding, flag);
}
int conn_mgr_if_get_timeout(struct net_if *iface)
{
struct conn_mgr_conn_binding *binding = conn_mgr_if_get_binding(iface);
int value;
if (!binding) {
return false;
}
conn_mgr_binding_lock(binding);
value = binding->timeout;
conn_mgr_binding_unlock(binding);
return value;
}
int conn_mgr_if_set_timeout(struct net_if *iface, int timeout)
{
struct conn_mgr_conn_binding *binding = conn_mgr_if_get_binding(iface);
if (!binding) {
return -ENOTSUP;
}
conn_mgr_binding_lock(binding);
binding->timeout = timeout;
conn_mgr_binding_unlock(binding);
return 0;
}
/* Automated behavior handling */
/**
* @brief Perform automated behaviors in response to ifaces going admin-up.
*
* @param iface - The iface which became admin-up.
*/
static void conn_mgr_conn_handle_iface_admin_up(struct net_if *iface)
{
int err;
/* Ignore ifaces that don't have connectivity implementations */
if (!conn_mgr_if_is_bound(iface)) {
return;
}
/* Ignore ifaces for which auto-connect is disabled */
if (conn_mgr_if_get_flag(iface, CONN_MGR_IF_NO_AUTO_CONNECT)) {
return;
}
/* Otherwise, automatically instruct the iface to connect */
err = conn_mgr_if_connect(iface);
if (err < 0) {
NET_ERR("iface auto-connect failed: %d", err);
}
}
/**
* @brief Take the provided iface admin-down.
*
* Called automatically by conn_mgr when an iface has lost connection and will not attempt to
* regain it.
*
* @param iface - The iface to take admin-down
*/
static void conn_mgr_conn_if_auto_admin_down(struct net_if *iface)
{
/* NOTE: This will be double-fired for ifaces that are both non-persistent
* and are being directly requested to disconnect, since both of these conditions
* separately trigger conn_mgr_conn_if_auto_admin_down.
*
* This is fine, because net_if_down is idempotent, but if you are adding other
* behaviors to this function, bear it in mind.
*/
/* Ignore ifaces that don't have connectivity implementations */
if (!conn_mgr_if_is_bound(iface)) {
return;
}
/* Take the iface admin-down if AUTO_DOWN is enabled */
if (IS_ENABLED(CONFIG_NET_CONNECTION_MANAGER_AUTO_IF_DOWN) &&
!conn_mgr_if_get_flag(iface, CONN_MGR_IF_NO_AUTO_DOWN)) {
net_if_down(iface);
}
}
/**
* @brief Perform automated behaviors in response to any iface that loses oper-up state.
*
* This is how conn_mgr_conn automatically takes such ifaces admin-down if they are not persistent.
*
* @param iface - The iface which lost oper-up state.
*/
static void conn_mgr_conn_handle_iface_down(struct net_if *iface)
{
/* Ignore ifaces that don't have connectivity implementations */
if (!conn_mgr_if_is_bound(iface)) {
return;
}
/* If the iface is persistent, we expect it to try to reconnect, so nothing else to do */
if (conn_mgr_if_get_flag(iface, CONN_MGR_IF_PERSISTENT)) {
return;
}
/* Otherwise, we do not expect the iface to reconnect, and we should call
* conn_mgr_conn_if_auto_admin_down
*/
conn_mgr_conn_if_auto_admin_down(iface);
}
static struct net_mgmt_event_callback conn_mgr_conn_iface_cb;
static void conn_mgr_conn_iface_handler(struct net_mgmt_event_callback *cb, uint32_t mgmt_event,
struct net_if *iface)
{
if ((mgmt_event & CONN_MGR_CONN_IFACE_EVENTS_MASK) != mgmt_event) {
return;
}
switch (mgmt_event) {
case NET_EVENT_IF_DOWN:
conn_mgr_conn_handle_iface_down(iface);
break;
case NET_EVENT_IF_ADMIN_UP:
conn_mgr_conn_handle_iface_admin_up(iface);
break;
}
}
static struct net_mgmt_event_callback conn_mgr_conn_self_cb;
static void conn_mgr_conn_self_handler(struct net_mgmt_event_callback *cb, uint32_t mgmt_event,
struct net_if *iface)
{
if ((mgmt_event & CONN_MGR_CONN_SELF_EVENTS_MASK) != mgmt_event) {
return;
}
switch (NET_MGMT_GET_COMMAND(mgmt_event)) {
case NET_EVENT_CONN_CMD_IF_FATAL_ERROR:
if (cb->info) {
NET_ERR("Fatal connectivity error on iface %d (%p). Reason: %d.",
net_if_get_by_iface(iface), iface, *((int *)cb->info)
);
} else {
NET_ERR("Unknown fatal connectivity error on iface %d (%p).",
net_if_get_by_iface(iface), iface
);
}
__fallthrough;
case NET_EVENT_CONN_CMD_IF_TIMEOUT:
/* If a timeout or fatal error occurs, we do not expect the iface to try to
* reconnect, so call conn_mgr_conn_if_auto_admin_down.
*/
conn_mgr_conn_if_auto_admin_down(iface);
break;
}
}
void conn_mgr_conn_init(void)
{
/* Initialize connectivity bindings. */
STRUCT_SECTION_FOREACH(conn_mgr_conn_binding, binding) {
if (!(binding->impl->api)) {
LOG_ERR("Connectivity implementation has NULL API, and will be treated as "
"non-existent.");
} else if (binding->impl->api->init) {
conn_mgr_binding_lock(binding);
/* Set initial default values for binding state */
binding->timeout = CONN_MGR_IF_NO_TIMEOUT;
/* Call binding initializer */
binding->impl->api->init(binding);
conn_mgr_binding_unlock(binding);
}
}
/* Set up event listeners for automated behaviors */
net_mgmt_init_event_callback(&conn_mgr_conn_iface_cb, conn_mgr_conn_iface_handler,
CONN_MGR_CONN_IFACE_EVENTS_MASK);
net_mgmt_add_event_callback(&conn_mgr_conn_iface_cb);
net_mgmt_init_event_callback(&conn_mgr_conn_self_cb, conn_mgr_conn_self_handler,
CONN_MGR_CONN_SELF_EVENTS_MASK);
net_mgmt_add_event_callback(&conn_mgr_conn_self_cb);
/* Trigger any initial automated behaviors for ifaces */
STRUCT_SECTION_FOREACH(conn_mgr_conn_binding, binding) {
if (binding->impl->api) {
/* We need to fire conn_mgr_conn_handle_iface_admin_up for any
* (connectivity-enabled) ifaces that went admin-up before we registerred
* the event callback that typically handles this.
*/
if (net_if_is_admin_up(binding->iface)) {
conn_mgr_conn_handle_iface_admin_up(binding->iface);
}
}
}
}
enum conn_mgr_conn_all_if_oper {
ALL_IF_UP,
ALL_IF_DOWN,
ALL_IF_CONNECT,
ALL_IF_DISCONNECT
};
struct conn_mgr_conn_all_if_ctx {
bool skip_ignored;
enum conn_mgr_conn_all_if_oper operation;
int status;
};
/* Per-iface callback for conn_mgr_conn_all_if_up */
static void conn_mgr_conn_all_if_cb(struct net_if *iface, void *user_data)
{
int status = 0;
struct conn_mgr_conn_all_if_ctx *context = (struct conn_mgr_conn_all_if_ctx *)user_data;
/* Skip ignored ifaces if so desired */
if (context->skip_ignored && conn_mgr_is_iface_ignored(iface)) {
return;
}
/* Perform the requested operation */
switch (context->operation) {
case ALL_IF_UP:
/* Do not take iface admin up if it already is. */
if (net_if_is_admin_up(iface)) {
return;
}
status = net_if_up(iface);
break;
case ALL_IF_DOWN:
/* Do not take iface admin down if it already is. */
if (!net_if_is_admin_up(iface)) {
return;
}
status = net_if_down(iface);
break;
case ALL_IF_CONNECT:
/* Connect operation only supported if iface is bound */
if (!conn_mgr_if_is_bound(iface)) {
return;
}
status = conn_mgr_if_connect(iface);
break;
case ALL_IF_DISCONNECT:
/* Disconnect operation only supported if iface is bound */
if (!conn_mgr_if_is_bound(iface)) {
return;
}
status = conn_mgr_if_disconnect(iface);
break;
}
if (status == 0) {
return;
}
if (context->status == 0) {
context->status = status;
}
NET_ERR("%s failed for iface %d (%p). Error: %d",
context->operation == ALL_IF_UP ? "net_if_up" :
context->operation == ALL_IF_DOWN ? "net_if_down" :
context->operation == ALL_IF_CONNECT ? "conn_mgr_if_connect" :
context->operation == ALL_IF_DISCONNECT ? "conn_mgr_if_disconnect" :
"invalid",
net_if_get_by_iface(iface), iface, status
);
}
int conn_mgr_all_if_up(bool skip_ignored)
{
struct conn_mgr_conn_all_if_ctx context = {
.operation = ALL_IF_UP,
.skip_ignored = skip_ignored,
.status = 0
};
net_if_foreach(conn_mgr_conn_all_if_cb, &context);
return context.status;
}
int conn_mgr_all_if_down(bool skip_ignored)
{
struct conn_mgr_conn_all_if_ctx context = {
.operation = ALL_IF_DOWN,
.skip_ignored = skip_ignored,
.status = 0
};
net_if_foreach(conn_mgr_conn_all_if_cb, &context);
return context.status;
}
int conn_mgr_all_if_connect(bool skip_ignored)
{
/* First, take all ifaces up.
* All bound ifaces will do this automatically when connect is called, but non-bound ifaces
* won't, so we must request it explicitly.
*/
struct conn_mgr_conn_all_if_ctx context = {
.operation = ALL_IF_UP,
.skip_ignored = skip_ignored,
.status = 0
};
net_if_foreach(conn_mgr_conn_all_if_cb, &context);
/* Now connect all ifaces.
* We are delibarately not resetting context.status between these two calls so that
* the first nonzero status code encountered between the two of them is what is returned.
*/
context.operation = ALL_IF_CONNECT;
net_if_foreach(conn_mgr_conn_all_if_cb, &context);
return context.status;
}
int conn_mgr_all_if_disconnect(bool skip_ignored)
{
struct conn_mgr_conn_all_if_ctx context = {
.operation = ALL_IF_DISCONNECT,
.skip_ignored = skip_ignored,
.status = 0
};
net_if_foreach(conn_mgr_conn_all_if_cb, &context);
return context.status;
}