net: conn_mgr: Support Auto-Down

To reduce the amount of boiler-plate needed in applications, this commit
grants conn_mgr the ability to automatically take ifaces that have given
up on connecting into the admin-down state.

Tests adjusted as appropriate.

This behavior can be disabled globally by disabling
NET_CONNECTION_MANAGER_AUTO_IF_DOWN, or disabled per-iface using the
CONN_MGR_IF_NO_AUTO_DOWN flag.

Signed-off-by: Georges Oates_Larsen <georges.larsen@nordicsemi.no>
This commit is contained in:
Georges Oates_Larsen 2023-04-26 16:30:23 -07:00 committed by Carles Cufí
parent f3d75c4c65
commit b65613e79c
8 changed files with 367 additions and 47 deletions

View file

@ -186,6 +186,12 @@ enum conn_mgr_if_flag {
*/
CONN_MGR_IF_NO_AUTO_CONNECT,
/* No auto-down
* When set, conn_mgr will not automatically take the iface admin-down when it stops
* trying to connect, even if NET_CONNECTION_MANAGER_AUTO_IF_DOWN is enabled.
*/
CONN_MGR_IF_NO_AUTO_DOWN,
/** @cond INTERNAL_HIDDEN */
/* Total number of flags - must be at the end of the enum */
CONN_MGR_NUM_IF_FLAGS,

View file

@ -36,4 +36,8 @@ config NET_CONNECTION_MANAGER_PRIORITY
help
This sets the starting priority of the connection manager thread.
config NET_CONNECTION_MANAGER_AUTO_IF_DOWN
bool "Automatically call net_if_down on ifaces that have given up on connecting"
default y
endif # NET_CONNECTION_MANAGER

View file

@ -48,6 +48,8 @@ out:
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;
@ -77,6 +79,16 @@ int conn_mgr_if_disconnect(struct net_if *iface)
out:
k_mutex_unlock(binding->mutex);
/* 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;
}
@ -263,6 +275,61 @@ static void conn_mgr_conn_handle_iface_admin_up(struct net_if *iface)
}
}
/**
* @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)
@ -272,12 +339,45 @@ static void conn_mgr_conn_iface_handler(struct net_mgmt_event_callback *cb, uint
}
switch (mgmt_event) {
case NET_EVENT_IF_UP:
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. */
@ -305,6 +405,10 @@ void conn_mgr_conn_init(void)
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) {

View file

@ -37,7 +37,11 @@
#define CONN_MGR_IFACE_EVENTS_MASK (NET_EVENT_IF_DOWN | \
NET_EVENT_IF_UP)
#define CONN_MGR_CONN_IFACE_EVENTS_MASK (NET_EVENT_IF_ADMIN_UP)
#define CONN_MGR_CONN_IFACE_EVENTS_MASK (NET_EVENT_IF_ADMIN_UP |\
NET_EVENT_IF_DOWN)
#define CONN_MGR_CONN_SELF_EVENTS_MASK (NET_EVENT_CONN_IF_TIMEOUT | \
NET_EVENT_CONN_IF_FATAL_ERROR)
#define CONN_MGR_IPV6_EVENTS_MASK (NET_EVENT_IPV6_ADDR_ADD | \
NET_EVENT_IPV6_ADDR_DEL | \

View file

@ -44,9 +44,6 @@ static void reset_test_iface(struct net_if *iface)
struct in6_addr *ll_ipv6;
if (net_if_is_admin_up(iface)) {
if (conn_mgr_if_is_bound(iface)) {
(void)conn_mgr_if_disconnect(iface);
}
(void)net_if_down(iface);
}
@ -206,10 +203,6 @@ static void cycle_ready_ifaces(struct net_if *ifa, struct net_if *ifb)
"No events should be fired if connectivity availability did not change.");
/* Take A down */
if (conn_mgr_if_is_bound(ifa)) {
zassert_equal(conn_mgr_if_disconnect(ifa), 0,
"conn_mgr_if_disconnect should succeed for ifa.");
}
zassert_equal(net_if_down(ifa), 0, "net_if_down should succeed for ifa.");
/* Expect no events */
@ -219,10 +212,6 @@ static void cycle_ready_ifaces(struct net_if *ifa, struct net_if *ifb)
"No events should be fired if connectivity availability did not change.");
/* Take B down */
if (conn_mgr_if_is_bound(ifb)) {
zassert_equal(conn_mgr_if_disconnect(ifb), 0,
"conn_mgr_if_disconnect should succeed for ifb.");
}
zassert_equal(net_if_down(ifb), 0, "net_if_down should succeed for ifb.");
/* Expect connectivity loss */
@ -266,10 +255,6 @@ static void cycle_ignored_iface(struct net_if *ifa, struct net_if *ifb)
"No events should be fired if connectivity availability did not change.");
/* Take B down */
if (conn_mgr_if_is_bound(ifb)) {
zassert_equal(conn_mgr_if_disconnect(ifb), 0,
"conn_mgr_if_disconnect should succeed for ifb.");
}
zassert_equal(net_if_down(ifb), 0, "net_if_down should succeed for ifb.");
/* Expect no events */
@ -300,10 +285,6 @@ static void cycle_ignored_iface(struct net_if *ifa, struct net_if *ifb)
"No events should be fired if connectivity availability did not change.");
/* Take B down */
if (conn_mgr_if_is_bound(ifb)) {
zassert_equal(conn_mgr_if_disconnect(ifb), 0,
"conn_mgr_if_disconnect should succeed for ifba.");
}
zassert_equal(net_if_down(ifb), 0, "net_if_down should succeed for ifb.");
/* Expect no events */
@ -313,10 +294,6 @@ static void cycle_ignored_iface(struct net_if *ifa, struct net_if *ifb)
"No events should be fired if connectivity availability did not change.");
/* Take A down */
if (conn_mgr_if_is_bound(ifa)) {
zassert_equal(conn_mgr_if_disconnect(ifa), 0,
"conn_mgr_if_disconnect should succeed for ifa.");
}
zassert_equal(net_if_down(ifa), 0, "net_if_down should succeed for ifa.");
/* Expect connectivity lost */
@ -353,10 +330,6 @@ static void cycle_ignored_iface(struct net_if *ifa, struct net_if *ifb)
zassert_equal(stats.conn_iface, ifa, "ifa should be blamed.");
/* Take B down */
if (conn_mgr_if_is_bound(ifb)) {
zassert_equal(conn_mgr_if_disconnect(ifb), 0,
"conn_mgr_if_disconnect should succeed for ifb.");
}
zassert_equal(net_if_down(ifb), 0, "net_if_down should succeed for ifb.");
/* Expect no events */
@ -368,10 +341,6 @@ static void cycle_ignored_iface(struct net_if *ifa, struct net_if *ifb)
/* Take B up */
zassert_equal(net_if_up(ifb), 0, "net_if_up should succeed for ifb.");
if (conn_mgr_if_is_bound(ifb)) {
zassert_equal(conn_mgr_if_connect(ifb), 0,
"conn_mgr_if_connect should succeed for ifb.");
}
/* Expect no events */
k_sleep(EVENT_WAIT_TIME);
@ -392,10 +361,6 @@ static void cycle_ignored_iface(struct net_if *ifa, struct net_if *ifb)
zassert_equal(stats.dconn_iface, ifa, "ifa should be blamed.");
/* Take B down */
if (conn_mgr_if_is_bound(ifb)) {
zassert_equal(conn_mgr_if_disconnect(ifb), 0,
"conn_mgr_if_disconnect should succeed for ifb.");
}
zassert_equal(net_if_down(ifb), 0, "net_if_down should succeed for ifb.");
/* Expect no events */
@ -566,10 +531,6 @@ static void cycle_iface_states(struct net_if *iface, enum ip_order ifa_ipm)
/* (10 -> 00): Lose oper-up from semi-ready state */
/* Take iface down */
if (conn_mgr_if_is_bound(iface)) {
zassert_equal(conn_mgr_if_disconnect(iface), 0,
"conn_mgr_if_disconnect should succeed.");
}
zassert_equal(net_if_down(iface), 0, "net_if_down should succeed.");
/* Verify there are no events fired */
@ -619,10 +580,6 @@ static void cycle_iface_states(struct net_if *iface, enum ip_order ifa_ipm)
/* (11 -> 01): Lose oper-up from ready state */
/* Take iface down */
if (conn_mgr_if_is_bound(iface)) {
zassert_equal(conn_mgr_if_disconnect(iface), 0,
"conn_mgr_if_disconnect should succeed.");
}
zassert_equal(net_if_down(iface), 0, "net_if_down should succeed.");
/* Verify events are fired */

View file

@ -59,8 +59,9 @@ static void reset_test_iface_state(struct net_if *iface)
iface_binding->flags = 0;
iface_binding->timeout = CONN_MGR_IF_NO_TIMEOUT;
/* Disable auto-connect */
/* Disable auto-connect and auto-down */
conn_mgr_if_set_flag(iface, CONN_MGR_IF_NO_AUTO_CONNECT, true);
conn_mgr_if_set_flag(iface, CONN_MGR_IF_NO_AUTO_DOWN, true);
}
if (iface_data) {
@ -919,4 +920,224 @@ ZTEST(conn_mgr_conn, test_auto_connect)
zassert_true(net_if_is_up(ifa1), "Auto-connect should succeed if enabled.");
}
/* Verify that if auto-down is enabled, disconnecting an iface also takes it down,
* regardless of whether persistence is enabled, but only if auto-down is disabled.
*/
ZTEST(conn_mgr_conn, test_auto_down_disconnect)
{
/* For convenience, use auto-connect for this test. */
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_NO_AUTO_CONNECT, false);
/* Enable auto-down, disable persistence */
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_PERSISTENT, false);
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_NO_AUTO_DOWN, false);
/* Take iface up */
zassert_equal(net_if_up(ifa1), 0, "net_if_up should succeed.");
/* Verify connected */
k_sleep(K_MSEC(1));
zassert_true(net_if_is_up(ifa1), "Connection should succeed.");
/* Disconnect iface */
zassert_equal(conn_mgr_if_disconnect(ifa1), 0,
"conn_mgr_if_disconnect should succeed.");
/* Verify down */
k_sleep(K_MSEC(1));
zassert_false(net_if_is_admin_up(ifa1),
"Auto-down should trigger on direct disconnect.");
/* Enable persistence */
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_PERSISTENT, true);
/* Take iface up */
zassert_equal(net_if_up(ifa1), 0, "net_if_up should succeed.");
/* Verify connected */
k_sleep(K_MSEC(1));
zassert_true(net_if_is_up(ifa1), "Connection should succeed.");
/* Disconnect iface */
zassert_equal(conn_mgr_if_disconnect(ifa1), 0,
"conn_mgr_if_disconnect should succeed.");
/* Verify down */
k_sleep(K_MSEC(1));
zassert_false(net_if_is_admin_up(ifa1),
"Auto-down should trigger on direct disconnect, even if persistence is enabled.");
/* Disable auto-down */
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_NO_AUTO_DOWN, true);
/* Take iface up */
zassert_equal(net_if_up(ifa1), 0, "net_if_up should succeed.");
/* Verify connected */
k_sleep(K_MSEC(1));
zassert_true(net_if_is_up(ifa1), "Connection should succeed.");
/* Disconnect iface */
zassert_equal(conn_mgr_if_disconnect(ifa1), 0,
"conn_mgr_if_disconnect should succeed.");
/* Verify up */
zassert_true(net_if_is_admin_up(ifa1),
"Auto-down should not trigger if it is disabled.");
}
/* Verify that auto-down takes an iface down if connection is lost, but only if persistence is not
* enabled, and only if auto-down is enabled.
*/
ZTEST(conn_mgr_conn, test_auto_down_conn_loss)
{
/* For convenience, use auto-connect for this test. */
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_NO_AUTO_CONNECT, false);
/* Enable auto-down, disable persistence */
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_PERSISTENT, false);
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_NO_AUTO_DOWN, false);
/* Take iface up */
zassert_equal(net_if_up(ifa1), 0, "net_if_up should succeed.");
/* Verify connected */
k_sleep(K_MSEC(1));
zassert_true(net_if_is_up(ifa1), "Connection should succeed.");
/* Simulate connection loss */
simulate_connection_loss(ifa1);
/* Verify down */
k_sleep(K_MSEC(1));
zassert_false(net_if_is_admin_up(ifa1),
"Auto-down should trigger on connection loss if persistence is disabled.");
/* Enable persistence */
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_PERSISTENT, true);
/* Take iface up */
zassert_equal(net_if_up(ifa1), 0, "net_if_up should succeed.");
/* Verify connected */
k_sleep(K_MSEC(1));
zassert_true(net_if_is_up(ifa1), "Connection should succeed.");
/* Simulate connection loss */
simulate_connection_loss(ifa1);
/* Verify up */
k_sleep(K_MSEC(1));
zassert_true(net_if_is_admin_up(ifa1),
"Auto-down should not trigger on connection loss if persistence is enabled.");
/* Disable persistence and disable auto-down*/
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_PERSISTENT, false);
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_NO_AUTO_DOWN, true);
/* Reconnect iface */
zassert_equal(conn_mgr_if_connect(ifa1), 0, "conn_mgr_if_connect should succeed.");
/* Verify connected */
k_sleep(K_MSEC(1));
zassert_true(net_if_is_up(ifa1), "Connection should succeed.");
/* Simulate connection loss */
simulate_connection_loss(ifa1);
/* Verify up */
k_sleep(K_MSEC(1));
zassert_true(net_if_is_admin_up(ifa1),
"Auto-down should not trigger on connection loss if it is disabled.");
}
/* Verify that timeout takes the iface down, even if
* persistence is enabled, but only if auto-down is enabled.
*/
ZTEST(conn_mgr_conn, test_auto_down_timeout)
{
struct test_conn_data *ifa1_data = conn_mgr_if_get_data(ifa1);
/* For convenience, use auto-connect for this test. */
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_NO_AUTO_CONNECT, false);
/* Enable auto-down and persistence*/
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_PERSISTENT, true);
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_NO_AUTO_DOWN, false);
/* Schedule timeout */
ifa1_data->timeout = true;
/* Take iface up */
zassert_equal(net_if_up(ifa1), 0, "net_if_up should succeed.");
/* Verify iface down after timeout */
k_sleep(SIMULATED_EVENT_WAIT_TIME);
zassert_false(net_if_is_admin_up(ifa1),
"Auto-down should trigger on connection timeout, even if persistence is enabled.");
/* Disable auto-down */
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_NO_AUTO_DOWN, true);
/* Take iface up (timing out again) */
zassert_equal(net_if_up(ifa1), 0, "net_if_up should succeed.");
/* Verify iface up after timeout */
k_sleep(SIMULATED_EVENT_WAIT_TIME);
zassert_true(net_if_is_admin_up(ifa1),
"Auto-down should not trigger on connection timeout if it is disabled.");
}
/* Verify that fatal error takes the iface down, even if
* persistence is enabled, but only if auto-down is enabled.
*/
ZTEST(conn_mgr_conn, test_auto_down_fatal)
{
/* For convenience, use auto-connect for this test. */
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_NO_AUTO_CONNECT, false);
/* Enable auto-down and persistence */
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_PERSISTENT, true);
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_NO_AUTO_DOWN, false);
/* Take iface up */
zassert_equal(net_if_up(ifa1), 0, "net_if_up should succeed.");
/* Verify connected */
k_sleep(K_MSEC(1));
zassert_true(net_if_is_up(ifa1), "Connection should succeed.");
/* Raise fatal error */
simulate_fatal_error(ifa1, -EAGAIN);
/* Verify iface down after fatal error */
k_sleep(SIMULATED_EVENT_WAIT_TIME);
zassert_false(net_if_is_admin_up(ifa1),
"Auto-down should trigger on fatal error, even if persistence is enabled.");
/* Disable auto-down */
conn_mgr_if_set_flag(ifa1, CONN_MGR_IF_NO_AUTO_DOWN, true);
/* Take iface up */
zassert_equal(net_if_up(ifa1), 0, "net_if_up should succeed.");
/* Verify connected */
k_sleep(K_MSEC(1));
zassert_true(net_if_is_up(ifa1), "Connection should succeed.");
/* Raise fatal error */
simulate_fatal_error(ifa1, -EAGAIN);
/* Verify iface still up after fatal error */
k_sleep(SIMULATED_EVENT_WAIT_TIME);
zassert_true(net_if_is_admin_up(ifa1),
"Auto-down should not trigger on fatal error if it is disabled.");
}
ZTEST_SUITE(conn_mgr_conn, NULL, conn_mgr_conn_setup, conn_mgr_conn_before, NULL, NULL);

View file

@ -67,11 +67,16 @@ static void simulate_timeout(struct net_if *target)
simulate_event(target, 0);
}
static void simulate_fatal_error(struct net_if *target, int reason)
void simulate_fatal_error(struct net_if *target, int reason)
{
simulate_event(target, reason);
}
void simulate_connection_loss(struct net_if *target)
{
net_if_dormant_on(target);
}
/* Connectivity implementations */
static void inc_call_count(struct test_conn_data *data, bool a)

View file

@ -80,6 +80,25 @@ CONN_MGR_CONN_DECLARE_PUBLIC(TEST_L2_CONN_IMPL_NI);
#define SIMULATED_EVENT_DELAY_TIME K_MSEC(SIMULATED_EVENT_DELAY_MS)
#define SIMULATED_EVENT_WAIT_TIME K_MSEC(SIMULATED_EVENT_DELAY_MS + 10)
/* Helper API for controlling implementations from tests */
/**
* @brief Simulate a connection loss on the target iface.
*
* @param target - iface to simulate connection loss on.
*/
void simulate_connection_loss(struct net_if *target);
/**
* @brief Simulate a fatal error on the target iface
*
* Please do not simulate events on more than one iface at a time.
*
* @param target - iface to simulate fatal error on.
* @param reason - Reason to be given for the fatal error.
*/
void simulate_fatal_error(struct net_if *target, int reason);
#ifdef __cplusplus
}
#endif