| /* |
| * 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; |
| } |