diff --git a/doc/connectivity/networking/conn_mgr/main.rst b/doc/connectivity/networking/conn_mgr/main.rst index 2bd20923c6c80..e8db873e6324d 100644 --- a/doc/connectivity/networking/conn_mgr/main.rst +++ b/doc/connectivity/networking/conn_mgr/main.rst @@ -294,6 +294,14 @@ You can individually set them for each iface. It is left to connectivity implementations to successfully and accurately implement these two features as described below. See :ref:`conn_mgr_impl_timeout_persistence` for more details from the connectivity implementation perspective. +The Connection Manager also implements the following optional feature: + +* :ref:`Interface idle timeouts ` + +.. note:: + The only requirement on the connectivity implementation to implement idle timeouts is to call :c:func:`conn_mgr_if_used` each + time the interface is used. + .. _conn_mgr_control_timeouts: Connection Timeouts @@ -306,6 +314,16 @@ The connection attempt continues indefinitely until it succeeds, unless a timeou In that case, the connection attempt will be abandoned if the timeout elapses before it succeeds. If this happens, the :ref:`timeout event` is raised. +.. _conn_mgr_control_idle_timeout: + +Interface Idle Timeout +---------------------- + +The connection manager enables users to apply an inactivity timeout on an interface (:c:func:`conn_mgr_if_set_idle_timeout`). +Once connected, if the interface goes for the configured number of seconds without any activity, the interface is automatically disconnected. +If this happens, the :ref:`idle timeout event` is raised. +An idle timeout is considered an unintentional connection loss for the purposes of :ref:`Connection persistence `. + .. _conn_mgr_control_persistence: Connection Persistence @@ -357,6 +375,15 @@ The :c:macro:`NET_EVENT_CONN_IF_TIMEOUT` event is raised when an :ref:`iface ass Handlers of this event will be passed a pointer to the iface that timed out attempting to associate. +.. _conn_mgr_control_events_idle_timeout: + +Idle Timeout +------------ + +The :c:macro:`NET_EVENT_CONN_IF_IDLE_TIMEOUT` event is raised when an interface is considered :ref:`inactive `. + +Handlers of this event will be passed a pointer to the iface that timed out attempting to associate. + .. _conn_mgr_control_events_listening: Listening for control events diff --git a/drivers/net/nsos_sockets.c b/drivers/net/nsos_sockets.c index fdf5a7e8bbfe8..9f50512cda5d1 100644 --- a/drivers/net/nsos_sockets.c +++ b/drivers/net/nsos_sockets.c @@ -62,6 +62,9 @@ struct nsos_socket { static sys_dlist_t nsos_polls = SYS_DLIST_STATIC_INIT(&nsos_polls); +/* Forward declaration of the interface */ +NET_IF_DECLARE(nsos_socket, 0); + static int socket_family_to_nsos_mid(int family, int *family_mid) { switch (family) { @@ -693,6 +696,7 @@ static int nsos_connect_blocking(struct nsos_socket *sock, static int nsos_connect(void *obj, const struct sockaddr *addr, socklen_t addrlen) { + struct net_if *iface = NET_IF_GET(nsos_socket, 0); struct nsos_socket *sock = obj; struct nsos_mid_sockaddr_storage addr_storage_mid; struct nsos_mid_sockaddr *addr_mid = (struct nsos_mid_sockaddr *)&addr_storage_mid; @@ -718,6 +722,7 @@ static int nsos_connect(void *obj, const struct sockaddr *addr, socklen_t addrle errno = nsi_errno_from_mid(-ret); return -1; } + conn_mgr_if_used(iface); return ret; } @@ -738,6 +743,7 @@ static int nsos_listen(void *obj, int backlog) static int nsos_accept(void *obj, struct sockaddr *addr, socklen_t *addrlen) { + struct net_if *iface = NET_IF_GET(nsos_socket, 0); struct nsos_socket *accept_sock = obj; struct nsos_mid_sockaddr_storage addr_storage_mid; struct nsos_mid_sockaddr *addr_mid = (struct nsos_mid_sockaddr *)&addr_storage_mid; @@ -782,6 +788,7 @@ static int nsos_accept(void *obj, struct sockaddr *addr, socklen_t *addrlen) zvfs_finalize_typed_fd(zephyr_fd, conn_sock, &nsos_socket_fd_op_vtable.fd_vtable, ZVFS_MODE_IFSOCK); + conn_mgr_if_used(iface); return zephyr_fd; @@ -799,6 +806,7 @@ static int nsos_accept(void *obj, struct sockaddr *addr, socklen_t *addrlen) static ssize_t nsos_sendto(void *obj, const void *buf, size_t len, int flags, const struct sockaddr *addr, socklen_t addrlen) { + struct net_if *iface = NET_IF_GET(nsos_socket, 0); struct nsos_socket *sock = obj; struct nsos_mid_sockaddr_storage addr_storage_mid; struct nsos_mid_sockaddr *addr_mid = (struct nsos_mid_sockaddr *)&addr_storage_mid; @@ -832,11 +840,13 @@ static ssize_t nsos_sendto(void *obj, const void *buf, size_t len, int flags, return -1; } + conn_mgr_if_used(iface); return ret; } static ssize_t nsos_sendmsg(void *obj, const struct msghdr *msg, int flags) { + struct net_if *iface = NET_IF_GET(nsos_socket, 0); struct nsos_socket *sock = obj; struct nsos_mid_sockaddr_storage addr_storage_mid; struct nsos_mid_sockaddr *addr_mid = (struct nsos_mid_sockaddr *)&addr_storage_mid; @@ -893,12 +903,14 @@ static ssize_t nsos_sendmsg(void *obj, const struct msghdr *msg, int flags) return -1; } + conn_mgr_if_used(iface); return ret; } static ssize_t nsos_recvfrom(void *obj, void *buf, size_t len, int flags, struct sockaddr *addr, socklen_t *addrlen) { + struct net_if *iface = NET_IF_GET(nsos_socket, 0); struct nsos_socket *sock = obj; struct nsos_mid_sockaddr_storage addr_storage_mid; struct nsos_mid_sockaddr *addr_mid = (struct nsos_mid_sockaddr *)&addr_storage_mid; @@ -932,6 +944,7 @@ static ssize_t nsos_recvfrom(void *obj, void *buf, size_t len, int flags, return -1; } + conn_mgr_if_used(iface); return ret; } @@ -1492,6 +1505,7 @@ static int nsos_getaddrinfo(const char *node, const char *service, const struct zsock_addrinfo *hints, struct zsock_addrinfo **res) { + struct net_if *iface = NET_IF_GET(nsos_socket, 0); struct nsos_mid_addrinfo hints_mid; struct nsos_mid_addrinfo *res_mid; int system_errno; @@ -1525,6 +1539,7 @@ static int nsos_getaddrinfo(const char *node, const char *service, errno = -ret; return DNS_EAI_SYSTEM; } + conn_mgr_if_used(iface); return ret; } @@ -1651,6 +1666,14 @@ static int nsos_net_if_disconnect(struct conn_mgr_conn_binding *const binding) LOG_INF("NSOS: dormant"); k_work_cancel_delayable(&data->work); net_if_dormant_on(binding->iface); + + if (conn_mgr_binding_get_flag(binding, CONN_MGR_IF_PERSISTENT) && + !conn_mgr_binding_get_flag(binding, CONN_MGR_IF_DISCONNECTING)) { + /* Interface marked as persistent, application didn't request the disconnect */ + LOG_INF("NSOS: reconnecting"); + k_work_reschedule(&data->work, data->connect_delay); + } + return 0; } diff --git a/include/zephyr/net/conn_mgr_connectivity.h b/include/zephyr/net/conn_mgr_connectivity.h index cb0f63fcfac01..12fbbc097521f 100644 --- a/include/zephyr/net/conn_mgr_connectivity.h +++ b/include/zephyr/net/conn_mgr_connectivity.h @@ -44,6 +44,7 @@ extern "C" { enum { NET_EVENT_CONN_CMD_IF_TIMEOUT_VAL, NET_EVENT_CONN_CMD_IF_FATAL_ERROR_VAL, + NET_EVENT_CONN_CMD_IF_IDLE_TIMEOUT_VAL, NET_EVENT_CONN_CMD_MAX }; @@ -54,6 +55,7 @@ BUILD_ASSERT(NET_EVENT_CONN_CMD_MAX <= NET_MGMT_MAX_COMMANDS, enum net_event_conn_cmd { NET_MGMT_CMD(NET_EVENT_CONN_CMD_IF_TIMEOUT), NET_MGMT_CMD(NET_EVENT_CONN_CMD_IF_FATAL_ERROR), + NET_MGMT_CMD(NET_EVENT_CONN_CMD_IF_IDLE_TIMEOUT), }; /** @endcond */ @@ -70,6 +72,12 @@ enum net_event_conn_cmd { #define NET_EVENT_CONN_IF_FATAL_ERROR \ (NET_MGMT_CONN_IF_EVENT | NET_EVENT_CONN_CMD_IF_FATAL_ERROR) +/** + * @brief net_mgmt event raised when an interface times out due to inactivity + */ +#define NET_EVENT_CONN_IF_IDLE_TIMEOUT \ + (NET_MGMT_CONN_IF_EVENT | NET_EVENT_CONN_CMD_IF_IDLE_TIMEOUT) + /** * @brief Per-iface connectivity flags @@ -264,6 +272,45 @@ int conn_mgr_if_get_timeout(struct net_if *iface); */ int conn_mgr_if_set_timeout(struct net_if *iface, int timeout); +/** + * @brief Get the idle timeout for an iface + * + * If the provided iface is bound to a connectivity implementation, retrieves the idle timeout + * setting in seconds for it. + * + * @param iface - Pointer to the iface to check. + * @return int - The connectivity timeout value (in seconds) if it could be retrieved, otherwise + * CONN_MGR_IF_NO_TIMEOUT. + */ +int conn_mgr_if_get_idle_timeout(struct net_if *iface); + +/** + * @brief Set the idle timeout for an iface. + * + * If the provided iface is bound to a connectivity implementation, sets the idle timeout setting + * in seconds for it. + * + * @param iface - Pointer to the network interface to modify. + * @param timeout - The timeout value to set (in seconds). + * Pass @ref CONN_MGR_IF_NO_TIMEOUT to disable the timeout. + * @retval 0 on success. + * @retval -ENOTSUP if the provided iface is not bound to a connectivity implementation. + */ +int conn_mgr_if_set_idle_timeout(struct net_if *iface, int timeout); + +#if defined(CONFIG_NET_CONNECTION_MANAGER) || defined(__DOXYGEN__) +/** + * @brief Notify connection manager that interface was just used + * + * @note Typically called from network drivers, not application software. + * + * @param iface iface that was just used + */ +void conn_mgr_if_used(struct net_if *iface); +#else +#define conn_mgr_if_used(iface) (void)(iface) +#endif /* defined(CONFIG_NET_CONNECTION_MANAGER) || defined(__DOXYGEN__) */ + /** * @} */ diff --git a/include/zephyr/net/conn_mgr_connectivity_impl.h b/include/zephyr/net/conn_mgr_connectivity_impl.h index 0c71c48f62c59..f2ca96e127159 100644 --- a/include/zephyr/net/conn_mgr_connectivity_impl.h +++ b/include/zephyr/net/conn_mgr_connectivity_impl.h @@ -193,9 +193,22 @@ struct conn_mgr_conn_binding { */ int timeout; + /** + * Usage timeout (seconds) + * + * Indicates to the connectivity implementation how long the interface can be idle + * for before automatically taking the interface down. + * + * Set to @ref CONN_MGR_IF_NO_TIMEOUT to indicate that no idle timeout should be used. + */ + int idle_timeout; + /** @} */ /** @cond INTERNAL_HIDDEN */ + /* Internal-use work item for tracking interface idle timeouts */ + struct k_work_delayable idle_worker; + /* Internal-use mutex for protecting access to the binding and API functions. */ struct k_mutex *mutex; /** @endcond */ diff --git a/include/zephyr/net/net_if.h b/include/zephyr/net/net_if.h index dd9e2df04d643..7f403c8ef466e 100644 --- a/include/zephyr/net/net_if.h +++ b/include/zephyr/net/net_if.h @@ -3495,6 +3495,16 @@ extern int net_stats_prometheus_scrape(struct prometheus_collector *collector, /* Network device initialization macros */ +/** + * @brief Forward declaration of a network interface + * + * Enables to use of `NET_IF_GET` above the instantiation macro. + * + * @param dev_id Device ID provided to `NET_IF_INIT` or `NET_IF_OFFLOAD_INIT` + */ +#define NET_IF_DECLARE(dev_id, inst) \ + static struct net_if NET_IF_GET_NAME(dev_id, inst)[NET_IF_MAX_CONFIGS] + #define Z_NET_DEVICE_INIT_INSTANCE(node_id, dev_id, name, instance, \ init_fn, pm, data, config, prio, \ api, l2, l2_ctx_type, mtu) \ diff --git a/subsys/net/conn_mgr/conn_mgr_connectivity.c b/subsys/net/conn_mgr/conn_mgr_connectivity.c index 905b5b07d1c59..a68bfd80ffc38 100644 --- a/subsys/net/conn_mgr/conn_mgr_connectivity.c +++ b/subsys/net/conn_mgr/conn_mgr_connectivity.c @@ -23,7 +23,7 @@ int conn_mgr_if_connect(struct net_if *iface) LOG_DBG("iface %p connect", iface); binding = conn_mgr_if_get_binding(iface); - if (!binding) { + if (binding == NULL) { return -ENOTSUP; } @@ -51,7 +51,7 @@ int conn_mgr_if_connect(struct net_if *iface) return status; } -int conn_mgr_if_disconnect(struct net_if *iface) +static int conn_mgr_if_disconnect_internal(struct net_if *iface, bool idle_timeout) { struct conn_mgr_conn_binding *binding; struct conn_mgr_conn_api *api; @@ -60,7 +60,7 @@ int conn_mgr_if_disconnect(struct net_if *iface) LOG_DBG("iface %p disconnect", iface); binding = conn_mgr_if_get_binding(iface); - if (!binding) { + if (binding == NULL) { return -ENOTSUP; } @@ -75,7 +75,9 @@ int conn_mgr_if_disconnect(struct net_if *iface) goto out; } - conn_mgr_if_set_flag(iface, CONN_MGR_IF_DISCONNECTING, true); + if (!idle_timeout) { + conn_mgr_if_set_flag(iface, CONN_MGR_IF_DISCONNECTING, true); + } status = api->disconnect(binding); @@ -85,6 +87,11 @@ int conn_mgr_if_disconnect(struct net_if *iface) return status; } +int conn_mgr_if_disconnect(struct net_if *iface) +{ + return conn_mgr_if_disconnect_internal(iface, false); +} + bool conn_mgr_if_is_bound(struct net_if *iface) { struct conn_mgr_conn_binding *binding = conn_mgr_if_get_binding(iface); @@ -108,7 +115,7 @@ int conn_mgr_if_get_opt(struct net_if *iface, int optname, void *optval, size_t } binding = conn_mgr_if_get_binding(iface); - if (!binding) { + if (binding == NULL) { *optlen = 0; return -ENOTSUP; } @@ -139,7 +146,7 @@ int conn_mgr_if_set_opt(struct net_if *iface, int optname, const void *optval, s } binding = conn_mgr_if_get_binding(iface); - if (!binding) { + if (binding == NULL) { return -ENOTSUP; } @@ -166,7 +173,7 @@ int conn_mgr_if_set_flag(struct net_if *iface, enum conn_mgr_if_flag flag, bool } binding = conn_mgr_if_get_binding(iface); - if (!binding) { + if (binding == NULL) { return -ENOTSUP; } @@ -184,7 +191,7 @@ bool conn_mgr_if_get_flag(struct net_if *iface, enum conn_mgr_if_flag flag) } binding = conn_mgr_if_get_binding(iface); - if (!binding) { + if (binding == NULL) { return false; } @@ -196,8 +203,8 @@ 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; + if (binding == NULL) { + return CONN_MGR_IF_NO_TIMEOUT; } conn_mgr_binding_lock(binding); @@ -213,7 +220,7 @@ 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) { + if (binding == NULL) { return -ENOTSUP; } @@ -226,6 +233,54 @@ int conn_mgr_if_set_timeout(struct net_if *iface, int timeout) return 0; } +int conn_mgr_if_get_idle_timeout(struct net_if *iface) +{ + struct conn_mgr_conn_binding *binding = conn_mgr_if_get_binding(iface); + int value; + + if (binding == NULL) { + return CONN_MGR_IF_NO_TIMEOUT; + } + + conn_mgr_binding_lock(binding); + + value = binding->idle_timeout; + + conn_mgr_binding_unlock(binding); + + return value; +} + +int conn_mgr_if_set_idle_timeout(struct net_if *iface, int timeout) +{ + struct conn_mgr_conn_binding *binding = conn_mgr_if_get_binding(iface); + + if (binding == NULL) { + return -ENOTSUP; + } + + conn_mgr_binding_lock(binding); + + binding->idle_timeout = timeout; + + conn_mgr_binding_unlock(binding); + + return 0; +} + +void conn_mgr_if_used(struct net_if *iface) +{ + struct conn_mgr_conn_binding *binding = conn_mgr_if_get_binding(iface); + + if (binding == NULL) { + return; + } + if (binding->idle_timeout == CONN_MGR_IF_NO_TIMEOUT) { + return; + } + k_work_reschedule(&binding->idle_worker, K_SECONDS(binding->idle_timeout)); +} + /* Automated behavior handling */ /** @@ -264,6 +319,8 @@ static void conn_mgr_conn_handle_iface_admin_up(struct net_if *iface) */ static void conn_mgr_conn_if_auto_admin_down(struct net_if *iface) { + struct conn_mgr_conn_binding *binding = conn_mgr_if_get_binding(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. @@ -273,10 +330,13 @@ static void conn_mgr_conn_if_auto_admin_down(struct net_if *iface) */ /* Ignore ifaces that don't have connectivity implementations */ - if (!conn_mgr_if_is_bound(iface)) { + if (binding == NULL) { return; } + /* Cancel any pending idle timeouts */ + k_work_cancel_delayable(&binding->idle_worker); + /* 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)) { @@ -284,6 +344,26 @@ static void conn_mgr_conn_if_auto_admin_down(struct net_if *iface) } } +static void conn_mgr_conn_handle_iface_up(struct net_if *iface) +{ + struct conn_mgr_conn_binding *binding = conn_mgr_if_get_binding(iface); + int idle_timeout; + + /* Ignore ifaces that don't have connectivity implementations */ + if (binding == NULL) { + return; + } + + idle_timeout = conn_mgr_if_get_idle_timeout(iface); + if (idle_timeout == CONN_MGR_IF_NO_TIMEOUT) { + /* No idle timeout configured for the interface */ + return; + } + + /* Start the idle timeout */ + k_work_reschedule(&binding->idle_worker, K_SECONDS(idle_timeout)); +} + /** * @brief Perform automated behaviors in response to any iface that loses oper-up state. * @@ -323,6 +403,9 @@ static void conn_mgr_conn_iface_handler(struct net_mgmt_event_callback *cb, uint } switch (mgmt_event) { + case NET_EVENT_IF_UP: + conn_mgr_conn_handle_iface_up(iface); + break; case NET_EVENT_IF_DOWN: conn_mgr_conn_handle_iface_down(iface); break; @@ -358,8 +441,22 @@ static void conn_mgr_conn_self_handler(struct net_mgmt_event_callback *cb, uint6 */ conn_mgr_conn_if_auto_admin_down(iface); break; + case NET_EVENT_CONN_CMD_IF_IDLE_TIMEOUT: + /* Interface is idle, disconnect */ + LOG_DBG("iface %d (%p) idle", net_if_get_by_iface(iface), iface); + conn_mgr_if_disconnect_internal(iface, true); + break; } +} +static void conn_mgr_iface_idle_fn(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct conn_mgr_conn_binding *binding = + CONTAINER_OF(dwork, struct conn_mgr_conn_binding, idle_worker); + + LOG_DBG("iface %d (%p) idle", net_if_get_by_iface(binding->iface), binding->iface); + net_mgmt_event_notify(NET_EVENT_CONN_IF_IDLE_TIMEOUT, binding->iface); } void conn_mgr_conn_init(void) @@ -372,9 +469,14 @@ void conn_mgr_conn_init(void) } else if (binding->impl->api->init) { conn_mgr_binding_lock(binding); + /* Initialise idle worker */ + + k_work_init_delayable(&binding->idle_worker, conn_mgr_iface_idle_fn); + /* Set initial default values for binding state */ binding->timeout = CONN_MGR_IF_NO_TIMEOUT; + binding->idle_timeout = CONN_MGR_IF_NO_TIMEOUT; /* Call binding initializer */ diff --git a/subsys/net/conn_mgr/conn_mgr_private.h b/subsys/net/conn_mgr/conn_mgr_private.h index 05dc28b4b4fe1..87532dde64490 100644 --- a/subsys/net/conn_mgr/conn_mgr_private.h +++ b/subsys/net/conn_mgr/conn_mgr_private.h @@ -40,10 +40,12 @@ NET_EVENT_IF_UP) #define CONN_MGR_CONN_IFACE_EVENTS_MASK (NET_EVENT_IF_ADMIN_UP |\ + NET_EVENT_IF_UP |\ NET_EVENT_IF_DOWN) #define CONN_MGR_CONN_SELF_EVENTS_MASK (NET_EVENT_CONN_IF_TIMEOUT | \ - NET_EVENT_CONN_IF_FATAL_ERROR) + NET_EVENT_CONN_IF_FATAL_ERROR | \ + NET_EVENT_CONN_IF_IDLE_TIMEOUT) #define CONN_MGR_IPV6_EVENTS_MASK (NET_EVENT_IPV6_ADDR_ADD | \ NET_EVENT_IPV6_ADDR_DEL | \ diff --git a/subsys/net/ip/net_core.c b/subsys/net/ip/net_core.c index 5c34912638fe6..eb9437ccb95dc 100644 --- a/subsys/net/ip/net_core.c +++ b/subsys/net/ip/net_core.c @@ -23,6 +23,7 @@ LOG_MODULE_REGISTER(net_core, CONFIG_NET_CORE_LOG_LEVEL); #include #include +#include #include #include #include @@ -471,6 +472,7 @@ static void net_rx(struct net_if *iface, struct net_pkt *pkt) NET_DBG("Received pkt %p len %zu", pkt, pkt_len); net_stats_update_bytes_recv(iface, pkt_len); + conn_mgr_if_used(iface); if (IS_ENABLED(CONFIG_NET_LOOPBACK)) { #ifdef CONFIG_NET_L2_DUMMY diff --git a/subsys/net/ip/net_if.c b/subsys/net/ip/net_if.c index 021de80c57eff..af13143d74b19 100644 --- a/subsys/net/ip/net_if.c +++ b/subsys/net/ip/net_if.c @@ -15,6 +15,7 @@ LOG_MODULE_REGISTER(net_if, CONFIG_NET_IF_LOG_LEVEL); #include #include #include +#include #include #include #include @@ -310,6 +311,7 @@ static bool net_if_tx(struct net_if *iface, struct net_pkt *pkt) net_pkt_unref(pkt); } else { net_stats_update_bytes_sent(iface, status); + conn_mgr_if_used(iface); } if (context) { diff --git a/tests/net/conn_mgr_conn/src/main.c b/tests/net/conn_mgr_conn/src/main.c index 940d142b8dbd5..364f3841a6c0c 100644 --- a/tests/net/conn_mgr_conn/src/main.c +++ b/tests/net/conn_mgr_conn/src/main.c @@ -897,6 +897,51 @@ ZTEST(conn_mgr_conn, test_timeout_invalid) "Getting timeout should yield CONN_MGR_IF_NO_TIMEOUT for ifnone"); } +/* Verify that idle timeout get/set functions operate correctly (A/B) */ +ZTEST(conn_mgr_conn, test_idle_timeout) +{ + struct conn_mgr_conn_binding *ifa1_binding = conn_mgr_if_get_binding(ifa1); + + /* Try setting idle timeout */ + zassert_equal(conn_mgr_if_set_idle_timeout(ifa1, 99), 0, + "Setting idle timeout should succeed for ifa1"); + + /* Verify success */ + zassert_equal(conn_mgr_if_get_idle_timeout(ifa1), 99, + "Idle timeout should be set to 99 for ifa1"); + + /* Verify that the conn struct agrees, since this is what implementations may use */ + zassert_equal(ifa1_binding->idle_timeout, 99, "Idle timeout set should affect conn struct"); + + /* Try unsetting idle timeout */ + zassert_equal(conn_mgr_if_set_idle_timeout(ifa1, CONN_MGR_IF_NO_TIMEOUT), 0, + "Unsetting idle timeout should succeed for ifa1"); + + /* Verify success */ + zassert_equal(conn_mgr_if_get_idle_timeout(ifa1), CONN_MGR_IF_NO_TIMEOUT, + "Idle timeout should be unset for ifa1"); + + /* Verify that the conn struct agrees, since this is what implementations may use */ + zassert_equal(ifa1_binding->idle_timeout, CONN_MGR_IF_NO_TIMEOUT, + "Idle timeout unset should affect conn struct"); +} + +/* Verify that idle timeout get/set fail and behave as expected respectively for invalid ifaces */ +ZTEST(conn_mgr_conn, test_idle_timeout_invalid) +{ + /* Verify set failure */ + zassert_equal(conn_mgr_if_set_idle_timeout(ifnull, 99), -ENOTSUP, + "Setting idle timeout should fail for ifnull"); + zassert_equal(conn_mgr_if_set_idle_timeout(ifnone, 99), -ENOTSUP, + "Setting idle timeout should fail for ifnone"); + + /* Verify get graceful behavior */ + zassert_equal(conn_mgr_if_get_idle_timeout(ifnull), CONN_MGR_IF_NO_TIMEOUT, + "Getting idle timeout should yield CONN_MGR_IF_NO_TIMEOUT for ifnull"); + zassert_equal(conn_mgr_if_get_idle_timeout(ifnone), CONN_MGR_IF_NO_TIMEOUT, + "Getting idle timeout should yield CONN_MGR_IF_NO_TIMEOUT for ifnone"); +} + /* Verify that auto-connect works as expected. */ ZTEST(conn_mgr_conn, test_auto_connect) { diff --git a/tests/net/conn_mgr_nsos/src/main.c b/tests/net/conn_mgr_nsos/src/main.c index d4e014accff8e..252bcc4ea43f2 100644 --- a/tests/net/conn_mgr_nsos/src/main.c +++ b/tests/net/conn_mgr_nsos/src/main.c @@ -12,6 +12,8 @@ #include #include +#include + K_SEM_DEFINE(l4_connected, 0, 1); K_SEM_DEFINE(l4_disconnected, 0, 1); @@ -125,10 +127,69 @@ ZTEST(conn_mgr_nsos, test_conn_mgr_nsos) 0, conn_mgr_if_set_opt(iface, 0, &conn_delay_default, sizeof(conn_delay_default))); } +ZTEST(conn_mgr_nsos, test_conn_mgr_nsos_idle) +{ + struct net_if *iface = net_if_get_default(); + struct sockaddr_in v4addr; + int sock, rc; + + /* 2 second idle timeout */ + conn_mgr_if_set_idle_timeout(iface, 2); + + /* Trigger the connection */ + zassert_equal(0, conn_mgr_if_connect(iface)); + zassert_equal(0, k_sem_take(&l4_connected, K_SECONDS(2))); + + /* Connection should terminate after 2 seconds due to inactivity */ + zassert_equal(-EAGAIN, k_sem_take(&l4_disconnected, K_MSEC(1900))); + zassert_equal(0, k_sem_take(&l4_disconnected, K_MSEC(500))); + + /* Connect again */ + zassert_equal(0, conn_mgr_if_connect(iface)); + zassert_equal(0, k_sem_take(&l4_connected, K_SECONDS(2))); + + /* Send data after a second (to localhost) */ + rc = zsock_inet_pton(AF_INET, "127.0.0.1", (void *)&v4addr); + zassert_equal(1, rc); + v4addr.sin_family = AF_INET; + v4addr.sin_port = htons(1234); + + sock = zsock_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + rc = zsock_sendto(sock, "TEST", 4, 0, (const struct sockaddr *)&v4addr, sizeof(v4addr)); + zassert_equal(4, rc); + + /* Should have reset the idle timeout */ + zassert_equal(-EAGAIN, k_sem_take(&l4_disconnected, K_MSEC(1900))); + zassert_equal(0, k_sem_take(&l4_disconnected, K_MSEC(500))); + + /* Set the interface to persistent */ + conn_mgr_if_set_flag(iface, CONN_MGR_IF_PERSISTENT, true); + + /* Trigger the connection */ + zassert_equal(0, conn_mgr_if_connect(iface)); + zassert_equal(0, k_sem_take(&l4_connected, K_SECONDS(2))); + + /* Interface should disconnect due to idle */ + zassert_equal(0, k_sem_take(&l4_disconnected, K_MSEC(2100))); + /* But it should also come back up automatically */ + zassert_equal(0, k_sem_take(&l4_connected, K_SECONDS(2))); + + /* Clear the persistent flag, times out and doesn't reconnect */ + conn_mgr_if_set_flag(iface, CONN_MGR_IF_PERSISTENT, false); + zassert_equal(0, k_sem_take(&l4_disconnected, K_MSEC(2100))); + zassert_equal(-EAGAIN, k_sem_take(&l4_connected, K_MSEC(2100))); + + /* Cleanup socket */ + zsock_close(sock); +} + static void test_init(void *state) { + struct net_if *iface = net_if_get_default(); + k_sem_take(&l4_connected, K_NO_WAIT); k_sem_take(&l4_disconnected, K_NO_WAIT); + conn_mgr_if_set_idle_timeout(iface, CONN_MGR_IF_NO_TIMEOUT); } static void test_after(void *fixture)