diff --git a/include/coap3/coap_net.h b/include/coap3/coap_net.h index 640cd5b719..e5061d0004 100644 --- a/include/coap3/coap_net.h +++ b/include/coap3/coap_net.h @@ -341,7 +341,17 @@ void coap_context_rate_limit_ppm(coap_context_t *context, * @param max_body_size The maximum supported body size. 0 indicates unlimited. */ void coap_context_set_max_body_size(coap_context_t *context, - uint32_t max_body_size); + size_t max_body_size); + +/** + * Set the maximum supported total pending bodies that are being received. + * + * @param context The coap_context_t object. + * @param max_bodies_ram The maximum supported ram usage by server's pending + * receive queues. 0 (the default) indicates unlimited. + */ +void coap_context_set_max_bodies_ram(coap_context_t *context, + size_t max_bodies_ram); /** * Set the maximum token size (RFC8974). diff --git a/include/coap3/coap_net_internal.h b/include/coap3/coap_net_internal.h index 9eab6d1959..fd2bc9295a 100644 --- a/include/coap3/coap_net_internal.h +++ b/include/coap3/coap_net_internal.h @@ -220,6 +220,10 @@ struct coap_context_t { uint8_t shutdown_no_send_observe; /**< Do not send out unsolicited observe when coap_free_context() is called. Otherwise 5.03 will get sent */ + uint32_t lg_srcv_cnt; /**< Number of active lg_srcv */ + size_t max_bodies_ram; /**< Max RAM usable for concurrent lg_srcv + 0 = unlimited */ + size_t cur_bodies_ram; /**< Current RAM in use for concurrent lg_srcv */ #endif /* COAP_SERVER_SUPPORT */ #if COAP_PROXY_SUPPORT coap_proxy_entry_t *proxy_list; /**< Set of active proxy sessions */ @@ -235,7 +239,7 @@ struct coap_context_t { coap_resource_dynamic_create_t dyn_create_handler; /**< Dynamic resource create handler */ uint32_t dynamic_cur; /**< Current number of dynamic resources */ uint32_t dynamic_max; /**< Max number of dynamic resources or 0 is unlimited */ - uint32_t max_body_size; /**< Max supported body size or 0 is unlimited */ + size_t max_body_size; /**< Max supported body size or 0 is unlimited */ #if COAP_THREAD_SAFE pthread_t *thread_id; /**< Set of current additional threads */ uint32_t thread_id_count; /**< Number of additional threads */ diff --git a/libcoap-3.map b/libcoap-3.map index ecf79e47e0..1f80026c0b 100644 --- a/libcoap-3.map +++ b/libcoap-3.map @@ -69,6 +69,7 @@ global: coap_context_set_csm_timeout_ms; coap_context_set_keepalive; coap_context_set_max_block_size; + coap_context_set_max_bodies_ram; coap_context_set_max_body_size; coap_context_set_max_handshake_sessions; coap_context_set_max_idle_sessions; diff --git a/libcoap-3.sym b/libcoap-3.sym index ec787d4250..7511e903ed 100644 --- a/libcoap-3.sym +++ b/libcoap-3.sym @@ -67,6 +67,7 @@ coap_context_set_csm_timeout coap_context_set_csm_timeout_ms coap_context_set_keepalive coap_context_set_max_block_size +coap_context_set_max_bodies_ram coap_context_set_max_body_size coap_context_set_max_handshake_sessions coap_context_set_max_idle_sessions diff --git a/man/Makefile.am b/man/Makefile.am index bcaa1365e3..6ef7c993ec 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -119,6 +119,7 @@ install-man: install-man3 install-man5 install-man7 @echo ".so man3/coap_address.3" > coap_is_mcast.3 @echo ".so man3/coap_address.3" > coap_is_af_unix.3 @echo ".so man3/coap_endpoint_server.3" > coap_endpoint_join_mcast_group_intf.3 + @echo ".so man3/coap_block.3" > coap_block_build_body.3 @echo ".so man3/coap_block.3" > coap_q_block_is_supported.3 @echo ".so man3/coap_block.3" > coap_register_block_data_handler.3 @echo ".so man3/coap_cache.3" > coap_cache_get_pdu.3 diff --git a/man/coap_block.txt.in b/man/coap_block.txt.in index 63806aa285..992f4ccb37 100644 --- a/man/coap_block.txt.in +++ b/man/coap_block.txt.in @@ -14,6 +14,7 @@ coap_block, coap_context_set_block_mode, coap_context_set_max_block_size, coap_context_set_max_body_size, +coap_context_set_max_bodies_ram, coap_add_data_large_request, coap_add_data_large_request_app, coap_add_data_large_response, @@ -34,7 +35,10 @@ uint32_t _block_mode_);* size_t _max_block_size_);* *void coap_context_set_max_body_size(coap_context_t *_context_, -uint32_t _max_body_size_);* +size_t _max_body_size_);* + +*void coap_context_set_max_bodies_ram(coap_context_t *_context_, +size_t _max_bodies_ram_);* *int coap_add_data_large_request(coap_session_t *_session_, coap_pdu_t *_pdu_, size_t _length_, const uint8_t *_data_, @@ -284,11 +288,34 @@ Block2 options are used. This must be set before a server session is created. *Function: coap_context_set_max_body_size()* The *coap_context_set_max_body_size*() function is used to set the -_max_body_size_ in the _context_ that a server or client supports when the Block1 or -Block2 options are used. This must be set before a server session is created. -_max_body_size_ specifies the maximum size that the peer can handle after -re-assembling all the individual blocks. If set to 0 (the default), -_max_body_size_ is treated as unlimited. +_max_body_size_ in the _context_ that a server or client supports when the (Q-)Block1 + or (Q-)Block2 options are used and COAP_BLOCK_SINGLE_BODY is set. This should be called +before a server session is created. _max_body_size_ specifies the maximum size that +the peer can handle after re-assembling all the individual blocks within a +transferred body. If set to 0 (the default), _max_body_size_ is treated as unlimited. + +*NOTE:* If COAP_BLOCK_SINGLE_BODY is not set, then it is up to the application to respond +with a 4.13 code (server) or RST (client) should a limit be hit as libcoap is not +tracking the size. + +*Function: coap_context_set_max_bodies_ram()* + +The *coap_context_set_max_bodies_ram*() function is used to set the +_max_bodies_ram_ in the _context_ that a server supports across all the pending +body transfers when the (Q-)Block1 option is used and COAP_BLOCK_SINGLE_BODY is set. +This should be called before a server session is created. _max_bodies_ram_ specifies +the maximum size that the server can handle after re-assembling all the individual +blocks across all the sessions. If set to 0 (the default), _max_bodies_ram_ is +treated as unlimited. + +*NOTE:* *coap_context_set_max_body_size*() sets the individual body maximum, +*coap_context_set_max_bodies_ram*() sets the sum of the bodies maximum. +Both can be used. If *coap_context_set_max_body_size*() is not used, then +_max_body_size_ is assumed to have the value of _max_bodies_ram_. + +*NOTE:* If COAP_BLOCK_SINGLE_BODY is not set, then it is up to the application to +respond with a 5.03 code should a limit be hit as libcoap is not tracking the +pending bodies summation. *Function: coap_add_data_large_request()* diff --git a/src/coap_block.c b/src/coap_block.c index c836396cdd..1bd789ec21 100644 --- a/src/coap_block.c +++ b/src/coap_block.c @@ -2095,6 +2095,17 @@ coap_block_check_lg_srcv_timeouts(coap_session_t *session, coap_tick_t now, #endif /* COAP_Q_BLOCK_SUPPORT */ partial_timeout = COAP_MAX_TRANSMIT_WAIT_TICKS(session); + if (session->context->lg_srcv_cnt > 5) { + /* In case someone is DOSing us. + * DEFAULT_NON_PARTIAL_TIMEOUT is 247 seconds. + * MAX_TRANSIT_WAIT is 93 seconds. + */ + partial_timeout /= (session->context->lg_srcv_cnt - 5); + if (partial_timeout < 10 * COAP_TICKS_PER_SECOND) { + /* Do not shrink it too much */ + partial_timeout = 10 * COAP_TICKS_PER_SECOND; + } + } LL_FOREACH_SAFE(session->lg_srcv, lg_srcv, q) { if (lg_srcv->dont_timeout) { /* Not safe to timeout at present */ @@ -2832,7 +2843,7 @@ coap_block_delete_lg_crcv(coap_session_t *session, return; } - coap_free_type(COAP_STRING, lg_crcv->body_data); + coap_delete_binary(lg_crcv->body_data); if (lg_crcv->obs_data) { coap_block_release_lg_xmit_data(session, lg_crcv->obs_data); lg_crcv->obs_data = NULL; @@ -2854,9 +2865,6 @@ coap_block_delete_lg_crcv(coap_session_t *session, void coap_block_delete_lg_srcv(coap_session_t *session, coap_lg_srcv_t *lg_srcv) { -#if (COAP_MAX_LOGGING_LEVEL < _COAP_LOG_DEBUG) - (void)session; -#endif if (lg_srcv == NULL) return; @@ -2867,10 +2875,13 @@ coap_block_delete_lg_srcv(coap_session_t *session, coap_delete_cache_key(lg_srcv->cache_key); coap_delete_bin_const(lg_srcv->last_token); - coap_free_type(COAP_STRING, lg_srcv->body_data); + if (lg_srcv->body_data) + session->context->cur_bodies_ram -= lg_srcv->body_data->length; + coap_delete_binary(lg_srcv->body_data); coap_log_debug("** %s: lg_srcv %p released\n", coap_session_str(session), (void *)lg_srcv); coap_free_type(COAP_LG_SRCV, lg_srcv); + session->context->lg_srcv_cnt--; } #endif /* COAP_SERVER_SUPPORT */ @@ -3467,15 +3478,15 @@ coap_handle_request_put_block(coap_context_t *context, (block_option == COAP_OPTION_BLOCK1 && block.num == 0 && block.m == 0)) { /* Not blocked, or a single block */ if (context->max_body_size && total > context->max_body_size) { - uint8_t buf[4]; + uint8_t buf[8]; coap_update_option(response, COAP_OPTION_SIZE1, - coap_encode_var_safe((uint8_t *)buf, sizeof(buf), - context->max_body_size), + coap_encode_var_safe8((uint8_t *)buf, sizeof(buf), + context->max_body_size), (uint8_t *)buf); response->code = COAP_RESPONSE_CODE(413); - coap_log_warn("Unable to handle data size %" PRIuS " (max %" PRIu32 ")\n", total, + coap_log_warn("Unable to handle data size %" PRIuS " (max %" PRIuS ")\n", total, context->max_body_size); goto skip_app_handler; } @@ -3540,20 +3551,20 @@ coap_handle_request_put_block(coap_context_t *context, (total > max_body)) { /* Suggested body size larger than allowed */ char buf[32]; - uint32_t max_body_size = context->max_body_size; + size_t max_body_size = context->max_body_size; if (max_body_size == 0 || max_body < max_body_size) { max_body_size = max_body; } coap_update_option(response, COAP_OPTION_SIZE1, - coap_encode_var_safe((uint8_t *)buf, sizeof(buf), - max_body_size), + coap_encode_var_safe8((uint8_t *)buf, sizeof(buf), + max_body_size), (uint8_t *)buf); - snprintf(buf, sizeof(buf), "Max body size %" PRIu32, max_body_size); + snprintf(buf, sizeof(buf), "Max body size %" PRIuS, max_body_size); coap_add_data(response, strlen(buf), (uint8_t *)buf); response->code = COAP_RESPONSE_CODE(413); - coap_log_warn("Unable to handle body size %" PRIuS " (max %" PRIu32 ")\n", total, max_body_size); + coap_log_warn("Unable to handle body size %" PRIuS " (max %" PRIuS ")\n", total, max_body_size); goto skip_app_handler; } } @@ -3634,10 +3645,12 @@ coap_handle_request_put_block(coap_context_t *context, } coap_log_debug("** %s: lg_srcv %p initialized\n", coap_session_str(session), (void *)lg_srcv); + session->context->lg_srcv_cnt++; memset(lg_srcv, 0, sizeof(coap_lg_srcv_t)); #if COAP_OSCORE_SUPPORT && COAP_SERVER_SUPPORT lg_srcv->recipient_ctx = session->recipient_ctx; #endif /* COAP_OSCORE_SUPPORT && COAP_SERVER_SUPPORT */ + coap_ticks(&lg_srcv->last_used); lg_srcv->resource = resource; lg_srcv->cache_key = cache_key_l; cache_key_l = NULL; @@ -3661,7 +3674,6 @@ coap_handle_request_put_block(coap_context_t *context, memcpy(lg_srcv->rtag, coap_opt_value(rtag_opt), lg_srcv->rtag_length); lg_srcv->rtag_set = 1; } - lg_srcv->body_data = NULL; #if COAP_Q_BLOCK_SUPPORT lg_srcv->r_m_payload_set = -1; #endif /* COAP_Q_BLOCK_SUPPORT */ @@ -3703,6 +3715,24 @@ coap_handle_request_put_block(coap_context_t *context, lg_srcv->last_type = pdu->type; + if (context->max_bodies_ram) { + ssize_t increase = total - (lg_srcv->body_data ? lg_srcv->body_data->length : 0); + + if (increase > 0 && context->cur_bodies_ram + increase > context->max_bodies_ram) { + uint8_t buf[4]; + + coap_update_option(response, COAP_OPTION_MAXAGE, + coap_encode_var_safe(buf, + sizeof(buf), + 5), + buf); + coap_add_data(response, sizeof("Memory limit hit")-1, + (const uint8_t *)"Memory limit hit"); + response->code = COAP_RESPONSE_CODE(503); + goto skip_app_handler; + } + } + update_data = 0; saved_num = block.num; saved_offset = offset; @@ -3763,6 +3793,8 @@ coap_handle_request_put_block(coap_context_t *context, ((session->block_mode & COAP_SINGLE_BLOCK_OR_Q) || block.bert) && \ (resource->flags & COAP_RESOURCE_USE_BLOCK_DATA_HANDLER)) + if (lg_srcv->body_data) + session->context->cur_bodies_ram -= lg_srcv->body_data->length; if (USE_BLOCK_DATA_HANDLER) { coap_response_t resp; @@ -3785,6 +3817,8 @@ coap_handle_request_put_block(coap_context_t *context, goto skip_app_handler; } } + if (lg_srcv->body_data) + context->cur_bodies_ram += lg_srcv->body_data->length; } else { #if COAP_Q_BLOCK_SUPPORT if (block_option == COAP_OPTION_Q_BLOCK1) { @@ -4499,7 +4533,7 @@ coap_block_build_body_lkd(coap_binary_t *body_data, size_t length, return NULL; /* Check no overflow (including a 8 byte small headroom) */ - if (SIZE_MAX - length < 8 || offset > SIZE_MAX - length - 8) { + if (SIZE_MAX - 8 < length || offset > SIZE_MAX - length - 8) { coap_delete_binary(body_data); return NULL; } @@ -4656,7 +4690,7 @@ coap_handle_response_get_block(coap_context_t *context, #endif /* COAP_Q_BLOCK_SUPPORT */ lg_crcv->initial = 0; if (lg_crcv->body_data) { - coap_free_type(COAP_STRING, lg_crcv->body_data); + coap_delete_binary(lg_crcv->body_data); lg_crcv->body_data = NULL; } if (etag_opt) { @@ -4693,12 +4727,12 @@ coap_handle_response_get_block(coap_context_t *context, max_body = MAX_BLK_LEN; if ((context->max_body_size && size2 > context->max_body_size) || (size2 > max_body)) { - uint32_t max_body_size = context->max_body_size; + size_t max_body_size = context->max_body_size; if (max_body_size == 0 || max_body < max_body_size) { max_body_size = max_body; } - coap_log_warn("Unable to handle body size %" PRIuS " (max %" PRIu32 ")\n", size2, max_body_size); + coap_log_warn("Unable to handle body size %" PRIuS " (max %" PRIuS ")\n", size2, max_body_size); /* Try to hint to the server there is an issue */ coap_send_rst_lkd(session, rcvd); coap_handle_event_lkd(session->context, COAP_EVENT_BLOCK_ISSUE, session); @@ -4726,7 +4760,7 @@ coap_handle_response_get_block(coap_context_t *context, coap_handle_event_lkd(context, COAP_EVENT_PARTIAL_BLOCK, session); lg_crcv->initial = 1; - coap_free_type(COAP_STRING, lg_crcv->body_data); + coap_delete_binary(lg_crcv->body_data); lg_crcv->body_data = NULL; coap_session_new_token(session, &len, buf); @@ -5017,7 +5051,7 @@ coap_handle_response_get_block(coap_context_t *context, COAP_OPTION_OBSERVE, &opt_iter); if (context->max_body_size && length > context->max_body_size) { - coap_log_warn("Unable to handle body size %" PRIuS " (max %" PRIu32 ")\n", length, + coap_log_warn("Unable to handle body size %" PRIuS " (max %" PRIuS ")\n", length, context->max_body_size); /* Try to hint to the server there is an issue */ coap_send_rst_lkd(session, rcvd); @@ -5175,7 +5209,7 @@ coap_handle_response_get_block(coap_context_t *context, } coap_get_data(rcvd, &length, &data); if (context->max_body_size && length > context->max_body_size) { - coap_log_warn("Unable to handle body size %" PRIuS " (max %" PRIu32 ")\n", length, + coap_log_warn("Unable to handle body size %" PRIuS " (max %" PRIuS ")\n", length, context->max_body_size); /* Try to hint to the server there is an issue */ coap_send_rst_lkd(session, rcvd); diff --git a/src/coap_net.c b/src/coap_net.c index 48eb418808..9d434d322a 100644 --- a/src/coap_net.c +++ b/src/coap_net.c @@ -480,13 +480,30 @@ coap_context_rate_limit_ppm(coap_context_t *context, void coap_context_set_max_body_size(coap_context_t *context, - uint32_t max_body_size) { + size_t max_body_size) { assert(max_body_size == 0 || max_body_size > 1024); if (max_body_size == 0 || max_body_size > 1024) { context->max_body_size = max_body_size; } } +void +coap_context_set_max_bodies_ram(coap_context_t *context, + size_t max_bodies_ram) { +#if COAP_SERVER_SUPPORT + assert(max_bodies_ram == 0 || max_bodies_ram > 1024); + if (max_bodies_ram == 0 || max_bodies_ram > 1024) { + context->max_bodies_ram = max_bodies_ram; + } + /* Cannot let a single session keep failing if it is larger then max_bodies_ram */ + if (context->max_body_size == 0) + context->max_body_size = max_bodies_ram; +#else + (void)context; + (void)max_bodies_ram; +#endif /* COAP_SERVER_SUPPORT */ +} + void coap_context_set_max_token_size(coap_context_t *context, size_t max_token_size) { @@ -4603,6 +4620,49 @@ handle_response(coap_context_t *context, coap_session_t *session, } #endif /* COAP_Q_BLOCK_SUPPORT */ + /* See if the server is struggling */ + if (rcvd->code == COAP_RESPONSE_CODE(503) || rcvd->code == COAP_RESPONSE_CODE(429)) { + if (!sent && session->lg_crcv) + sent = session->lg_crcv->sent_pdu; + + if (sent) { + /* sent is not active, so safe to reuse */ + uint8_t buf[4]; + /* Need to resend request in max_age seconds */ + size_t max_age = 60; + coap_opt_iterator_t opt_iter; + coap_opt_t *option = coap_check_option(rcvd, COAP_OPTION_MAXAGE, &opt_iter); + coap_queue_t *node = coap_new_node(); + if (option) + max_age = coap_decode_var_bytes(coap_opt_value(option), + coap_opt_length(option)); + + if (!node) { + coap_log_debug("retransmit delay: insufficient memory\n"); + return; + } + + coap_pdu_reference_lkd(sent); + sent->mid = coap_new_message_id_lkd(session); + coap_update_option(sent, + COAP_OPTION_RTAG, + coap_encode_var_safe(buf, sizeof(buf), + ++session->tx_rtag), + buf); + coap_pdu_encode_header(sent, session->proto); + node->id = sent->mid; + node->pdu = sent; + coap_log_debug(" %s: mid=0x%04x: re-request delayed for %u secs\n", + coap_session_str(session), + sent->mid, + (unsigned int)max_age); + node->timeout = (unsigned int)max_age * COAP_TICKS_PER_SECOND; + /* Use this to delay transmission */ + coap_wait_ack(session->context, session, node); + } + return; + } + if (session->block_mode & COAP_BLOCK_USE_LIBCOAP) { /* See if need to send next block to server */ if (coap_handle_response_send_block(session, sent, rcvd)) {