From 21b7a9a0eb73a577d7f6dcd8fd30a9823005293b Mon Sep 17 00:00:00 2001 From: Jon Shallow Date: Thu, 19 Mar 2026 16:25:41 +0000 Subject: [PATCH] Debugging: Add coap_debug_set_packet_fail() for testing network failures This is similar to coap_debug_set_packet_loss() which drops packets that are to be sent, but emulates network reported errors by reporting ECONNREFUSED. --- include/coap3/coap_debug.h | 15 +++++++++ libcoap-3.map | 1 + libcoap-3.sym | 1 + man/Makefile.am | 1 + man/coap_recovery.txt.in | 35 ++++++++++++++++++-- src/coap_debug.c | 66 ++++++++++++++++++++++++++++++++++++++ src/coap_dgrm_contiki.c | 8 +++-- src/coap_dgrm_lwip.c | 6 +++- src/coap_dgrm_posix.c | 9 ++++-- src/coap_dgrm_riot.c | 8 +++-- 10 files changed, 139 insertions(+), 11 deletions(-) diff --git a/include/coap3/coap_debug.h b/include/coap3/coap_debug.h index 5cdeeca3..af398b1b 100644 --- a/include/coap3/coap_debug.h +++ b/include/coap3/coap_debug.h @@ -408,6 +408,21 @@ const char *coap_print_ip_addr(const coap_address_t *address, */ int coap_debug_set_packet_loss(const char *loss_level); +/** + * Set the packet transmit fail level for testing. This can be in one of two forms. + * + * Percentage : 0% to 100%. Use the specified probability. + * 0% is send all packets, 100% is fail all packets. + * + * List: A comma separated list of numbers or number ranges that are the + * packets to fail. + * + * @param fail_level The defined fail level (percentage or list). + * + * @return @c 1 If fail level set, @c 0 if there is an error. + */ +int coap_debug_set_packet_fail(const char *fail_level); + #ifdef __cplusplus } #endif diff --git a/libcoap-3.map b/libcoap-3.map index d6792008..352e2268 100644 --- a/libcoap-3.map +++ b/libcoap-3.map @@ -79,6 +79,7 @@ global: coap_context_set_session_reconnect_time; coap_context_set_session_timeout; coap_context_set_shutdown_no_observe; + coap_debug_set_packet_fail; coap_debug_set_packet_loss; coap_decode_var_bytes8; coap_decode_var_bytes; diff --git a/libcoap-3.sym b/libcoap-3.sym index 941ba0b3..63a0f822 100644 --- a/libcoap-3.sym +++ b/libcoap-3.sym @@ -77,6 +77,7 @@ coap_context_set_session_reconnect_time coap_context_set_session_reconnect_time2 coap_context_set_session_timeout coap_context_set_shutdown_no_observe +coap_debug_set_packet_fail coap_debug_set_packet_loss coap_decode_var_bytes coap_decode_var_bytes8 diff --git a/man/Makefile.am b/man/Makefile.am index a47ea940..139a54c1 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -211,6 +211,7 @@ install-man: install-man3 install-man5 install-man7 @echo ".so man3/coap_recovery.3" > coap_session_set_probing_rate.3 @echo ".so man3/coap_recovery.3" > coap_session_get_probing_rate.3 @echo ".so man3/coap_recovery.3" > coap_debug_set_packet_loss.3 + @echo ".so man3/coap_recovery.3" > coap_debug_set_packet_fail.3 @echo ".so man3/coap_resource.3" > coap_delete_resource.3 @echo ".so man3/coap_resource.3" > coap_resource_set_mode.3 @echo ".so man3/coap_resource.3" > coap_resource_set_userdata.3 diff --git a/man/coap_recovery.txt.in b/man/coap_recovery.txt.in index 95faea81..f2c828a7 100644 --- a/man/coap_recovery.txt.in +++ b/man/coap_recovery.txt.in @@ -31,7 +31,8 @@ coap_session_set_nstart, coap_session_get_nstart, coap_session_set_probing_rate, coap_session_get_probing_rate, -coap_debug_set_packet_loss +coap_debug_set_packet_loss, +coap_debug_set_packet_fail - Work with CoAP packet transmissions SYNOPSIS @@ -94,6 +95,8 @@ uint32_t _value_)*; *int coap_debug_set_packet_loss(const char *_loss_level_)*; +*int coap_debug_set_packet_fail(const char *_fail_level_)*; + For specific (D)TLS library support, link with *-lcoap-@LIBCOAP_API_VERSION@-notls*, *-lcoap-@LIBCOAP_API_VERSION@-gnutls*, *-lcoap-@LIBCOAP_API_VERSION@-openssl*, *-lcoap-@LIBCOAP_API_VERSION@-mbedtls*, @@ -280,16 +283,39 @@ probing rate value (RFC7252). *Function: coap_debug_set_packet_loss()* -The *coap_debug_set_packet_loss*() function is uses to set the packet loss +The *coap_debug_set_packet_loss*() function is used to set the packet loss levels as defined in _loss_level_. _loss_level_ can be set as a percentage from "0%" to "100%". -Alternatively, it is possible to specify which packets of a packet sequence +Alternatively, it is possible to specify which UDP packets of a packet sequence are dropped. A definition of "1,5-9,11-20,101" means that packets 1, 5 through 9, 11 through 20 and 101 will get dropped. A maximum of 10 different packet sets is supported. The packet count is reset to 0 when *coap_debug_set_packet_loss*() is called. To remove any packet losses, set the _loss_level_ to "0%". +*Function: coap_debug_set_packet_fail()* + +The *coap_debug_set_packet_fail*() function is used to set the packet fail +levels (which generates a session disconnected for that transmitted packet) +as defined in _fail_level_. _fail_level_ can be set as a percentage from +"0%" to "100%". +Alternatively, it is possible to specify which UDP packets of a packet sequence +are failed. A definition of "1,5-9,11-20,101" means that packets 1, 5 +through 9, 11 through 20 and 101 will get failed. A maximum of 10 different +packet sets is supported. The packet count is reset to 0 when +*coap_debug_set_packet_fail*() is called. +To remove any packet failures, set the _fail_level_ to "0%". + +*NOTE: Patterns set by *coap_debug_set_packet_loss*() are tested before +*coap_debug_set_packet_fail*() when dropping / failing a PDU transmission. Thus +[source, c] +---- + coap_debug_set_packet_loss("1,3"); + coap_debug_set_packet_fail("100%"); +---- +would cause sent packets 1 and 3 to be dropped and packets 2 and 4-n to report +failures. + RETURN VALUES ------------- *coap_session_get_ack_random_factor*(), *coap_session_get_ack_timeout*(), @@ -302,6 +328,9 @@ respective current values. *coap_debug_set_packet_loss*() returns 0 if _loss_level_ does not parse correctly, otherwise 1 if successful. +*coap_debug_set_packet_fail*() returns 0 if _fail_level_ does not parse +correctly, otherwise 1 if successful. + TESTING ------- diff --git a/src/coap_debug.c b/src/coap_debug.c index 2ba2e5bf..d351de53 100644 --- a/src/coap_debug.c +++ b/src/coap_debug.c @@ -1373,6 +1373,9 @@ static struct packet_num_interval { static int num_packet_loss_intervals = 0; static uint16_t packet_loss_level = 0; static int send_packet_count = 0; +struct packet_num_interval packet_fail_intervals[10]; +static int num_packet_fail_intervals = 0; +static uint16_t packet_fail_level = 0; int coap_debug_set_packet_loss(const char *loss_level) { @@ -1436,6 +1439,67 @@ coap_debug_send_packet(void) { return 0; } } + if (num_packet_fail_intervals > 0) { + int i; + for (i = 0; i < num_packet_fail_intervals; i++) { + if (send_packet_count >= packet_fail_intervals[i].start && + send_packet_count <= packet_fail_intervals[i].end) { + coap_log_debug("Packet %u failed\n", send_packet_count); + errno = ECONNREFUSED; + return -1; + } + } + } + if (packet_fail_level > 0) { + uint16_t r = 0; + coap_prng_lkd((uint8_t *)&r, 2); + if (r < packet_fail_level) { + coap_log_debug("Packet %u failed\n", send_packet_count); + errno = ECONNREFUSED; + return -1; + } + } + return 1; +} + +int +coap_debug_set_packet_fail(const char *fail_level) { + const char *p = fail_level; + char *end = NULL; + int n = (int)strtol(p, &end, 10), i = 0; + if (end == p || n < 0) + return 0; + if (*end == '%') { + if (n > 100) + n = 100; + packet_fail_level = n * 0xffff / 100; + coap_log_debug("packet fail level set to %d%%\n", n); + } else { + if (n <= 0) + return 0; + while (i < 10) { + packet_fail_intervals[i].start = n; + if (*end == '-') { + p = end + 1; + n = (int)strtol(p, &end, 10); + if (end == p || n <= 0) + return 0; + } + packet_fail_intervals[i++].end = n; + if (*end == 0) + break; + if (*end != ',') + return 0; + p = end + 1; + n = (int)strtol(p, &end, 10); + if (end == p || n <= 0) + return 0; + } + if (i == 10) + return 0; + num_packet_fail_intervals = i; + } + send_packet_count = 0; return 1; } @@ -1447,5 +1511,7 @@ coap_debug_reset(void) { memset(&packet_loss_intervals, 0, sizeof(packet_loss_intervals)); num_packet_loss_intervals = 0; packet_loss_level = 0; + num_packet_fail_intervals = 0; + packet_fail_level = 0; send_packet_count = 0; } diff --git a/src/coap_dgrm_contiki.c b/src/coap_dgrm_contiki.c index 4095d6cc..7de66034 100644 --- a/src/coap_dgrm_contiki.c +++ b/src/coap_dgrm_contiki.c @@ -106,9 +106,13 @@ ssize_t coap_socket_send(coap_socket_t *sock, coap_session_t *session, const uint8_t *data, size_t datalen) { ssize_t bytes_written = 0; + int r; - if (!coap_debug_send_packet()) { - bytes_written = (ssize_t)datalen; + if ((r = coap_debug_send_packet()) != 1) { + if (r) + bytes_written = -1; + else + bytes_written = (ssize_t)datalen; } else { uip_udp_packet_sendto(sock->udp_conn, data, datalen, &session->addr_info.remote.addr, session->addr_info.remote.port); diff --git a/src/coap_dgrm_lwip.c b/src/coap_dgrm_lwip.c index 12936b5d..266abc32 100644 --- a/src/coap_dgrm_lwip.c +++ b/src/coap_dgrm_lwip.c @@ -311,8 +311,12 @@ coap_socket_send(coap_socket_t *sock, coap_session_t *session, const uint8_t *data, size_t data_len) { struct pbuf *pbuf; int err; + int r; - if (coap_debug_send_packet()) { + if ((r = coap_debug_send_packet()) != 1) { + if (r) + data_len = -1; + } else { pbuf = pbuf_alloc(PBUF_TRANSPORT, data_len, PBUF_RAM); if (pbuf == NULL) { errno = ENOMEM; diff --git a/src/coap_dgrm_posix.c b/src/coap_dgrm_posix.c index 132945cf..c0c7d104 100644 --- a/src/coap_dgrm_posix.c +++ b/src/coap_dgrm_posix.c @@ -478,13 +478,17 @@ ssize_t coap_socket_send(coap_socket_t *sock, coap_session_t *session, const uint8_t *data, size_t datalen) { ssize_t bytes_written = 0; + int r; #if COAP_CLIENT_SUPPORT coap_test_cid_tuple_change(session); #endif /* COAP_CLIENT_SUPPORT */ - if (!coap_debug_send_packet()) { - bytes_written = (ssize_t)datalen; + if ((r = coap_debug_send_packet()) != 1) { + if (r) + bytes_written = -1; + else + bytes_written = (ssize_t)datalen; } else if (sock->flags & COAP_SOCKET_CONNECTED) { #ifdef _WIN32 bytes_written = send(sock->fd, (const char *)data, (int)datalen, 0); @@ -494,7 +498,6 @@ coap_socket_send(coap_socket_t *sock, coap_session_t *session, } else { #if defined(_WIN32) DWORD dwNumberOfBytesSent = 0; - int r; #endif /* _WIN32 */ #ifdef HAVE_STRUCT_CMSGHDR /* a buffer large enough to hold all packet info types, ipv6 is the largest */ diff --git a/src/coap_dgrm_riot.c b/src/coap_dgrm_riot.c index 1fa15faa..abf0d1fb 100644 --- a/src/coap_dgrm_riot.c +++ b/src/coap_dgrm_riot.c @@ -41,9 +41,13 @@ coap_socket_send(coap_socket_t *sock, const uint8_t *data, size_t datalen) { ssize_t bytes_written = 0; + int r; - if (!coap_debug_send_packet()) { - bytes_written = (ssize_t)datalen; + if ((r = coap_debug_send_packet()) != 1) { + if (r) + bytes_written = -1; + else + bytes_written = (ssize_t)datalen; } else if (sock->flags & COAP_SOCKET_CONNECTED) { bytes_written = sock_udp_send(&sock->udp, data, datalen, NULL); } else {