From 4d84859620540c8baf06fa094abb9e0ae19da0e7 Mon Sep 17 00:00:00 2001 From: Jon Shallow Date: Thu, 9 Jan 2025 11:20:49 +0000 Subject: [PATCH] keepalive: Add in server keepalive support If coap_context_set_keepalive() is set in the server, CoAP keepalive packets will get sent for any CoAP session that is curently observing a resource. If the keepalive times out, then the observation(s) are canceled and the server session is deleted. --- examples/coap-server.c | 1 + include/coap3/coap_session.h | 2 +- include/coap3/coap_session_internal.h | 19 ++++++++++++++ man/coap_keepalive.txt.in | 7 +++++ src/coap_io.c | 38 ++++++++++++++++++++++++--- src/coap_net.c | 11 ++++---- src/coap_resource.c | 3 +++ src/coap_session.c | 14 +++++++--- 8 files changed, 83 insertions(+), 12 deletions(-) diff --git a/examples/coap-server.c b/examples/coap-server.c index 3d0f8f6f70..772dfcf8de 100644 --- a/examples/coap-server.c +++ b/examples/coap-server.c @@ -2563,6 +2563,7 @@ main(int argc, char **argv) { coap_mcast_per_resource(ctx); coap_context_set_block_mode(ctx, block_mode); coap_context_set_max_block_size(ctx, max_block_size); + coap_context_set_keepalive(ctx, 30); if (csm_max_message_size) coap_context_set_csm_max_message_size(ctx, csm_max_message_size); if (doing_tls_engine) { diff --git a/include/coap3/coap_session.h b/include/coap3/coap_session.h index 63f85f461b..27345fdf33 100644 --- a/include/coap3/coap_session.h +++ b/include/coap3/coap_session.h @@ -220,7 +220,7 @@ coap_context_t *coap_session_get_context(const coap_session_t *session); * * @return @c 1 if updated, @c 0 on failure. */ -int coap_session_set_type_client(coap_session_t *session); +COAP_API int coap_session_set_type_client(coap_session_t *session); /** * Set the session MTU. This is the maximum message size that can be sent, diff --git a/include/coap3/coap_session_internal.h b/include/coap3/coap_session_internal.h index 8eb4c46149..ea0db889ea 100644 --- a/include/coap3/coap_session_internal.h +++ b/include/coap3/coap_session_internal.h @@ -78,6 +78,7 @@ struct coap_session_t { UT_hash_handle hh; coap_addr_tuple_t addr_info; /**< remote/local address info */ int ifindex; /**< interface index */ + unsigned ref_subscriptions; /**< reference count of current subscriptions */ coap_socket_t sock; /**< socket object for the session, if any */ #if COAP_SERVER_SUPPORT @@ -378,6 +379,24 @@ void coap_session_establish(coap_session_t *session); */ coap_session_t *coap_session_reference_lkd(coap_session_t *session); +/** + * Set the session type to client. Typically used in a call-home server. + * The session initially needs to be of type COAP_SESSION_TYPE_SERVER, + * which is then made COAP_SESSION_TYPE_CLIENT. + * Note: If this function is successful, the session reference count is + * incremented and a subsequent coap_session_release() taking the + * reference count to 0 will cause the (now client) session to be freed off. + * Note: This function will fail for a DTLS server type session if done before + * the ClientHello is seen. + * + * Note: This function must be called in the locked state. + * + * @param session The CoAP session. + * + * @return @c 1 if updated, @c 0 on failure. + */ +int coap_session_set_type_client_lkd(coap_session_t *session); + /** * Decrement reference counter on a session. * Note that the session may be deleted as a result and should not be used diff --git a/man/coap_keepalive.txt.in b/man/coap_keepalive.txt.in index 461c05b8ab..7fd56dac39 100644 --- a/man/coap_keepalive.txt.in +++ b/man/coap_keepalive.txt.in @@ -60,11 +60,18 @@ If the keepalive fails to solicit a response, then this can be tracked by defining the handler to use by using *coap_register_event_handler*() which will be called with a reason of COAP_EVENT_KEEPALIVE_FAILURE. +*NOTE:* A keepalive will only be transmitted when there has been no traffic on +the session for _seconds_. It does not regularily repeat every _seconds_. + *NOTE:* As this may be used to keep an interim NAT device "warm", the exponentially increasing retransmit times for CON requests is limited to _seconds_, but the retransmit counter is not affected. Unless needed, _seconds_ shold not be set to less than 30. +*NOTE:* If this is used at the server end of a CoAP session (set up by using +*coap_new_endpoint*(3)), keepalives will only be transmitted for sessions +that have an active observe subscription. + SEE ALSO -------- *coap_handler*(3) diff --git a/src/coap_io.c b/src/coap_io.c index c861db6bda..8b36a4c294 100644 --- a/src/coap_io.c +++ b/src/coap_io.c @@ -1328,7 +1328,7 @@ coap_io_prepare_io_lkd(coap_context_t *ctx, unsigned int *num_sockets, coap_tick_t now) { coap_queue_t *nextpdu; - coap_session_t *s, *rtmp; + coap_session_t *s, *stmp; coap_tick_t timeout = 0; coap_tick_t s_timeout; #if COAP_SERVER_SUPPORT @@ -1403,7 +1403,7 @@ coap_io_prepare_io_lkd(coap_context_t *ctx, sockets[(*num_sockets)++] = &ep->sock; } #endif /* ! COAP_EPOLL_SUPPORT && ! WITH_LWIP && ! RIOT_VERSION */ - SESSIONS_ITER_SAFE(ep->sessions, s, rtmp) { + SESSIONS_ITER_SAFE(ep->sessions, s, stmp) { /* Check whether any idle server sessions should be released */ if (s->type == COAP_SESSION_TYPE_SERVER && s->ref == 0 && s->delayqueue == NULL && @@ -1411,6 +1411,7 @@ coap_io_prepare_io_lkd(coap_context_t *ctx, s->state == COAP_SESSION_STATE_NONE)) { coap_handle_event_lkd(ctx, COAP_EVENT_SERVER_SESSION_DEL, s); coap_session_free(s); + continue; } else { if (s->type == COAP_SESSION_TYPE_SERVER && s->ref == 0 && s->delayqueue == NULL) { @@ -1474,11 +1475,42 @@ coap_io_prepare_io_lkd(coap_context_t *ctx, release_1: coap_session_release_lkd(s); } + if (s->type == COAP_SESSION_TYPE_SERVER && + s->state == COAP_SESSION_STATE_ESTABLISHED && + s->ref_subscriptions && + ctx->ping_timeout > 0) { + /* Only do this if this session is observing */ + if (s->last_rx_tx + ctx->ping_timeout * COAP_TICKS_PER_SECOND <= now) { + /* Time to send a ping */ + if ((s->last_ping_mid = coap_session_send_ping_lkd(s)) == COAP_INVALID_MID) + /* Some issue - not safe to continue processing */ + continue; + if (s->last_ping > 0 && s->last_pong < s->last_ping) { + coap_handle_event_lkd(s->context, COAP_EVENT_KEEPALIVE_FAILURE, s); + coap_session_reference_lkd(s); + RESOURCES_ITER(s->context->resources, r) { + coap_cancel_all_messages(s->context, s, NULL); + coap_delete_observer(r, s, NULL); + } + coap_session_release_lkd(s); + /* Force session to go away */ + coap_session_set_type_client_lkd(s); + coap_session_release_lkd(s); + /* check the next session */ + continue; + } + s->last_rx_tx = now; + s->last_ping = now; + } + s_timeout = (s->last_rx_tx + ctx->ping_timeout * COAP_TICKS_PER_SECOND) - now; + if (timeout == 0 || s_timeout < timeout) + timeout = s_timeout; + } } } #endif /* COAP_SERVER_SUPPORT */ #if COAP_CLIENT_SUPPORT - SESSIONS_ITER_SAFE(ctx->sessions, s, rtmp) { + SESSIONS_ITER_SAFE(ctx->sessions, s, stmp) { if (s->type == COAP_SESSION_TYPE_CLIENT && s->state == COAP_SESSION_STATE_ESTABLISHED && ctx->ping_timeout > 0) { diff --git a/src/coap_net.c b/src/coap_net.c index 4b15849935..7f61e44975 100644 --- a/src/coap_net.c +++ b/src/coap_net.c @@ -1844,10 +1844,10 @@ coap_send_internal(coap_session_t *session, coap_pdu_t *pdu) { #if COAP_OSCORE_SUPPORT if (session->oscore_encryption && pdu->type != COAP_MESSAGE_RST && - !(pdu->type == COAP_MESSAGE_ACK && pdu->code == COAP_EMPTY_CODE)) { + !(pdu->type == COAP_MESSAGE_ACK && pdu->code == COAP_EMPTY_CODE) && + !(COAP_PROTO_RELIABLE(session->proto) && pdu->code == COAP_SIGNALING_CODE_PONG)) { /* Refactor PDU as appropriate RFC8613 */ - coap_pdu_t *osc_pdu = coap_oscore_new_pdu_encrypted_lkd(session, pdu, NULL, - 0); + coap_pdu_t *osc_pdu = coap_oscore_new_pdu_encrypted_lkd(session, pdu, NULL, 0); if (osc_pdu == NULL) { coap_log_warn("OSCORE: PDU could not be encrypted\n"); @@ -2773,7 +2773,7 @@ coap_cancel_all_messages(coap_context_t *context, coap_session_t *session, while (q) { if (q->session == session && - coap_binary_equal(&q->pdu->actual_token, token)) { + (!token || coap_binary_equal(&q->pdu->actual_token, token))) { *p = q->next; coap_log_debug("** %s: mid=0x%04x: removed (6)\n", coap_session_str(session), q->id); @@ -4282,7 +4282,8 @@ coap_dispatch(coap_context_t *context, coap_session_t *session, coap_remove_from_queue(&context->sendqueue, session, pdu->mid, &sent); if (sent) { - coap_cancel(context, sent); + if (!is_ping_rst) + coap_cancel(context, sent); if (!is_ping_rst && !is_ext_token_rst) { if (sent->pdu->type==COAP_MESSAGE_CON) { diff --git a/src/coap_resource.c b/src/coap_resource.c index 90d69823f7..7e83082a39 100644 --- a/src/coap_resource.c +++ b/src/coap_resource.c @@ -875,6 +875,7 @@ coap_add_observer(coap_resource_t *resource, } s->cache_key = cache_key; s->session = coap_session_reference_lkd(session); + session->ref_subscriptions++; /* add subscriber to resource */ LL_PREPEND(resource->subscribers, s); @@ -1022,6 +1023,8 @@ coap_delete_observer_internal(coap_resource_t *resource, coap_session_t *session if (resource->subscribers) { LL_DELETE(resource->subscribers, s); coap_session_release_lkd(session); + assert(session->ref_subscriptions > 0); + session->ref_subscriptions--; coap_delete_pdu(s->pdu); coap_delete_cache_key(s->cache_key); coap_free_type(COAP_SUBSCRIPTION, s); diff --git a/src/coap_session.c b/src/coap_session.c index cee303ead0..86ba30369e 100644 --- a/src/coap_session.c +++ b/src/coap_session.c @@ -1985,9 +1985,18 @@ coap_session_get_type(const coap_session_t *session) { return 0; } -#if COAP_CLIENT_SUPPORT -int +COAP_API int coap_session_set_type_client(coap_session_t *session) { + int ret; + + coap_lock_lock(session->context, return 0); + ret = coap_session_set_type_client_lkd(session); + coap_lock_unlock(session->context); + return ret; +} + +int +coap_session_set_type_client_lkd(coap_session_t *session) { #if COAP_SERVER_SUPPORT if (session && session->type == COAP_SESSION_TYPE_SERVER) { coap_session_reference_lkd(session); @@ -1999,7 +2008,6 @@ coap_session_set_type_client(coap_session_t *session) { #endif /* ! COAP_SERVER_SUPPORT */ return 0; } -#endif /* COAP_CLIENT_SUPPORT */ coap_session_state_t coap_session_get_state(const coap_session_t *session) {