diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 2329604424..f4996f0ede 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -41,6 +41,14 @@ ifneq (,$(filter congure_test,$(USEMODULE))) USEMODULE += fmt endif +ifneq (,$(filter congure_reno,$(USEMODULE))) + USEMODULE += congure_reno_methods +endif + +ifneq (,$(filter congure_reno_methods,$(USEMODULE))) + USEMODULE += seq +endif + ifneq (,$(filter eepreg,$(USEMODULE))) FEATURES_REQUIRED += periph_eeprom endif diff --git a/sys/congure/Kconfig b/sys/congure/Kconfig index ce189fc382..7971b41ac4 100644 --- a/sys/congure/Kconfig +++ b/sys/congure/Kconfig @@ -9,6 +9,7 @@ menu "CongURE congestion control abstraction" depends on USEMODULE_CONGURE rsource "mock/Kconfig" +rsource "reno/Kconfig" rsource "test/Kconfig" endmenu # CongURE congestion control abstraction @@ -22,6 +23,7 @@ menuconfig MODULE_CONGURE if MODULE_CONGURE rsource "mock/Kconfig" +rsource "reno/Kconfig" rsource "test/Kconfig" endif # MODULE_CONGURE diff --git a/sys/congure/Makefile b/sys/congure/Makefile index 37c69ac62f..78bd97f60e 100644 --- a/sys/congure/Makefile +++ b/sys/congure/Makefile @@ -1,6 +1,12 @@ ifneq (,$(filter congure_mock,$(USEMODULE))) DIRS += mock endif +ifneq (,$(filter congure_reno,$(USEMODULE))) + DIRS += reno +endif +ifneq (,$(filter congure_reno_methods,$(USEMODULE))) + DIRS += reno/methods +endif ifneq (,$(filter congure_test,$(USEMODULE))) DIRS += test endif diff --git a/sys/congure/reno/Kconfig b/sys/congure/reno/Kconfig new file mode 100644 index 0000000000..deab1f1c8d --- /dev/null +++ b/sys/congure/reno/Kconfig @@ -0,0 +1,14 @@ +config MODULE_CONGURE_RENO + bool "CongURE implementation of TCP Reno" + depends on MODULE_CONGURE + select MODULE_CONGURE_RENO_METHODS + +config MODULE_CONGURE_RENO_METHODS + bool "Send driver methods for the CongURE implementation of TCP Reno" + depends on MODULE_SEQ + help + Many other congestion control mechanisms are just adaptations of TCP + Reno, so this makes the methods of @ref sys_congure_reno available to + other @ref sys_congure modules. Use module `congure_reno_methods` to + only compile in these modules, but not the driver for + `congure_reno_snd_t` or @ref congure_reno_snd_setup(). diff --git a/sys/congure/reno/Makefile b/sys/congure/reno/Makefile new file mode 100644 index 0000000000..3247e09532 --- /dev/null +++ b/sys/congure/reno/Makefile @@ -0,0 +1,3 @@ +MODULE := congure_reno + +include $(RIOTBASE)/Makefile.base diff --git a/sys/congure/reno/congure_reno.c b/sys/congure/reno/congure_reno.c new file mode 100644 index 0000000000..49db20aec6 --- /dev/null +++ b/sys/congure/reno/congure_reno.c @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @author Martine Lenders + */ + +#include "congure/reno.h" + +static const congure_snd_driver_t _driver = { + .init = congure_reno_snd_init, + .inter_msg_interval = congure_reno_snd_inter_msg_interval, + .report_msg_sent = congure_reno_snd_report_msg_sent, + .report_msg_discarded = congure_reno_snd_report_msg_discarded, + .report_msgs_timeout = congure_reno_snd_report_msgs_timeout, + .report_msgs_lost = congure_reno_snd_report_msgs_lost, + .report_msg_acked = congure_reno_snd_report_msg_acked, + .report_ecn_ce = congure_reno_snd_report_ecn_ce, +}; + +void congure_reno_snd_setup(congure_reno_snd_t *c, + const congure_reno_snd_consts_t *consts) +{ + c->super.driver = &_driver; + c->consts = consts; +} + +/** @} */ diff --git a/sys/congure/reno/methods/Makefile b/sys/congure/reno/methods/Makefile new file mode 100644 index 0000000000..896c86c470 --- /dev/null +++ b/sys/congure/reno/methods/Makefile @@ -0,0 +1,3 @@ +MODULE := congure_reno_methods + +include $(RIOTBASE)/Makefile.base diff --git a/sys/congure/reno/methods/congure_reno_methods.c b/sys/congure/reno/methods/congure_reno_methods.c new file mode 100644 index 0000000000..8d1e798d34 --- /dev/null +++ b/sys/congure/reno/methods/congure_reno_methods.c @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2021 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @author Martine Lenders + */ + +#include +#include + +#include "clist.h" +#include "seq.h" + +#include "congure/reno.h" + +static int _snd_in_fast_retransmit(congure_snd_t *cong) +{ + congure_reno_snd_t *c = (congure_reno_snd_t *)cong; + + return (c->dup_acks >= c->consts->frthresh); +} + +static inline congure_wnd_size_t _calc_init_wnd(congure_reno_snd_t *c) +{ + /* see https://tools.ietf.org/html/rfc5681#section-3.1 */ + if (c->mss > c->consts->cwnd_upper) { + return 2 * c->mss; + } + else if (c->mss <= c->consts->cwnd_lower) { + return 4 * c->mss; + } + else { + return 3 * c->mss; + } +} + +static void _fr_cwnd_dec(congure_reno_snd_t *c) +{ + if (c->consts->fr_cwnd_dec) { + c->consts->fr_cwnd_dec(c); + } + else { + /* max(c->mss * 2, c->super.cwnd / 2) */ + c->ssthresh = ((c->mss * 4) > c->super.cwnd) + ? (c->mss * 2) : (c->super.cwnd / 2); + c->super.cwnd = c->ssthresh + (3 * c->mss); + } +} + +static void _enforce_fast_retransmit(congure_reno_snd_t *c) +{ + if (!_snd_in_fast_retransmit(&c->super)) { + c->dup_acks = c->consts->frthresh; + } + _fr_cwnd_dec(c); + c->consts->fr(c); +} + +static void _dec_flight_size(congure_reno_snd_t *c, unsigned msg_size) +{ + /* check for integer underflow */ + if ((c->in_flight_size - msg_size) > c->in_flight_size) { + c->in_flight_size = 0U; + } + else { + c->in_flight_size -= msg_size; + } +} + +void congure_reno_set_mss(congure_reno_snd_t *c, congure_wnd_size_t mss) +{ + c->mss = mss; + c->super.cwnd = _calc_init_wnd(c); +} + +void congure_reno_snd_init(congure_snd_t *cong, void *ctx) +{ + congure_reno_snd_t *c = (congure_reno_snd_t *)cong; + + c->super.ctx = ctx; + c->mss = c->consts->init_mss; + c->last_ack = UINT32_MAX; + c->super.cwnd = _calc_init_wnd(c); + c->ssthresh = c->consts->init_ssthresh; + c->dup_acks = 0; +} + +int32_t congure_reno_snd_inter_msg_interval(congure_snd_t *cong, + unsigned msg_size) +{ + (void)cong; + (void)msg_size; + return -1; +} + +void congure_reno_snd_report_msg_sent(congure_snd_t *cong, unsigned sent_size) +{ + congure_reno_snd_t *c = (congure_reno_snd_t *)cong; + + if ((c->in_flight_size + sent_size) < c->super.cwnd) { + c->in_flight_size += sent_size; + } + else { + /* state machine is dependent on flight size being smaller or equal + * to cwnd as such cap cwnd here, in case caller reports a message in + * flight that was marked as lost, but the caller is using a later + * message to send another ACK. */ + c->in_flight_size = c->super.cwnd; + } +} + +void congure_reno_snd_report_msg_discarded(congure_snd_t *cong, + unsigned msg_size) +{ + congure_reno_snd_t *c = (congure_reno_snd_t *)cong; + + assert(msg_size <= c->in_flight_size); + + _dec_flight_size(c, msg_size); +} + +int _check_resends(clist_node_t *node, void *ctx) +{ + congure_snd_msg_t *msg = (congure_snd_msg_t *)node; + + (void)ctx; + if (msg->resends == 0) { + return 1; + } + return 0; +} + +int _mark_msg_lost(clist_node_t *node, void *ctx) +{ + congure_snd_msg_t *msg = (congure_snd_msg_t *)node; + congure_reno_snd_t *c = (void *)ctx; + + _dec_flight_size(c, msg->size); + return 0; +} + +void congure_reno_snd_report_msgs_timeout(congure_snd_t *cong, + congure_snd_msg_t *msgs) +{ + congure_reno_snd_t *c = (congure_reno_snd_t *)cong; + + if (msgs) { + if (clist_foreach(&msgs->super, _check_resends, NULL)) { + /* see https://tools.ietf.org/html/rfc5681#section-3.1 equation 4 */ + c->ssthresh = ((c->in_flight_size / 2) > (c->mss * 2)) + ? (c->in_flight_size / 2) + : (c->mss * 2); + } + /* do decrementing of flight size _after_ ssthresh reduction, + * since we use the in_flight_size there */ + clist_foreach(&msgs->super, _mark_msg_lost, c); + /* > Furthermore, upon a timeout (as specified in [RFC2988]) cwnd + * > MUST be set to no more than the loss window, LW, which equals + * > 1 full-sized segment (regardless of the value of IW). */ + c->super.cwnd = c->mss; + } +} + +void congure_reno_snd_report_msgs_lost(congure_snd_t *cong, + congure_snd_msg_t *msgs) +{ + congure_reno_snd_t *c = (congure_reno_snd_t *)cong; + + clist_foreach(&msgs->super, _mark_msg_lost, c); + _enforce_fast_retransmit(c); +} + +void congure_reno_snd_report_msg_acked(congure_snd_t *cong, + congure_snd_msg_t *msg, + congure_snd_ack_t *ack) +{ + congure_reno_snd_t *c = (congure_reno_snd_t *)cong; + + if (seq32_compare(ack->id, c->last_ack) <= 0) { + /* check for duplicate ACK according to + * https://tools.ietf.org/html/rfc5681#section-2 + * An acknowledgment is considered a "duplicate" [...] when + * (a) the receiver of the ACK has outstanding data, */ + if ((c->in_flight_size > 0) && + /* (b) the incoming acknowledgment carries no data, */ + (ack->size == 0) && + /* (c) the SYN and FIN bits are both off */ + (ack->clean) && + /* (d) the acknowledgment number is equal to the greatest + * acknowledgment received on the given connection, and */ + (ack->id == c->last_ack) && + /* (e) the advertised window in the incoming acknowledgment equals + * the advertised window in the last incoming acknowledgment. */ + ((ack->wnd == 0) || (c->consts->same_wnd_adv(c, ack)))) { + c->dup_acks++; + if (_snd_in_fast_retransmit(cong)) { + _fr_cwnd_dec(c); + c->consts->fr(c); + } + } + } + else { + c->dup_acks = 0; + c->last_ack = ack->id; + if (c->super.cwnd < c->ssthresh) { + /* slow start */ + if (c->consts->ss_cwnd_inc) { + c->consts->ss_cwnd_inc(c); + } + else { + c->super.cwnd += (c->in_flight_size < c->mss) + ? c->in_flight_size + : c->mss; + } + } + else { + /* congestion avoidance */ + if (c->consts->ca_cwnd_inc) { + c->consts->ca_cwnd_inc(c); + } + else { + c->super.cwnd += c->mss; + } + } + assert(msg->size <= c->in_flight_size); + _dec_flight_size(c, msg->size); + } +} + +void congure_reno_snd_report_ecn_ce(congure_snd_t *cong, ztimer_now_t time) +{ + congure_reno_snd_t *c = (congure_reno_snd_t *)cong; + + /* see https://tools.ietf.org/html/rfc8311#section-4.1 */ + (void)time; + c->super.cwnd /= 2; + c->ssthresh -= c->mss; +} + +/** @} */ diff --git a/sys/include/congure/reno.h b/sys/include/congure/reno.h new file mode 100644 index 0000000000..5bc41fd82e --- /dev/null +++ b/sys/include/congure/reno.h @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2021 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_congure_reno CongURE implementation of TCP Reno + * @ingroup sys_congure + * @brief Implementation of the TCP Reno congestion control algorithm for + * the CongURE framework. + * + * @see [RFC 5681](https://tools.ietf.org/html/rfc5681) + * @{ + * + * @file + * @brief + * + * @author Martine S. Lenders + */ +#ifndef CONGURE_RENO_H +#define CONGURE_RENO_H + +#include + +#include "congure.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Forward declaration of state object for CongURE Reno. + */ +typedef struct congure_reno_snd congure_reno_snd_t; + +/** + * @brief Constants for the congestion control. + * + * Example usage: + * + * ~~~~~~~~~~~~~~~~ {.c} + * static const congure_reno_snd_consts_t consts = { + * .fr = _my_fast_retransmit, + * .same_wnd_adv = _my_same_window_advertised, + * .init_mss = 1460, + * .cwnd_upper = 2190, + * .cwnd_lower = 1095, + * .init_ssthresh = CONGURE_WND_SIZE_MAX, + * .frthresh = 3, + * } + * static congure_reno_snd_t cong; + * + * // ... + * congure_reno_snd_setup(&cong, &const); + * ~~~~~~~~~~~~~~~~ + */ +typedef struct { + /** + * @brief Callback to enter and perform fast retransmit + * + * @param[in] c The CongURE state object (callback context is at + * `c->super.ctx`) + */ + void (*fr)(congure_reno_snd_t *c); + + /** + * @brief Callback to check if the advertised window within an ACK is the + * same as in the context + * + * @param[in] c The CongURE state object (callback context is at + * `c->super.ctx`) + * @param[in] ack The ACK to check. + */ + bool (*same_wnd_adv)(congure_reno_snd_t *c, congure_snd_ack_t *ack); + + /** + * @brief Callback to increase congestion window in slow start + * + * Defaults to + * + * ~~~~~~~~~~ {.c} + * c->cwnd += c->mss; + * ~~~~~~~~~~ + * + * when set to NULL + * + * @param[in] c The CongURE state object (callback context is at + * `c->super.ctx`) + */ + void (*ss_cwnd_inc)(congure_reno_snd_t *c); + + /** + * @brief Callback to increase congestion window in congestion avoidance + * + * Defaults to + * + * ~~~~~~~~~~ {.c} + * c->cwnd += (c->in_flight_size < c->mss) + * ? c->in_flight_size + * : c->mss; + * ~~~~~~~~~~ + * + * when set to NULL + * + * @param[in] c The CongURE state object (callback context is at + * `c->super.ctx`) + */ + void (*ca_cwnd_inc)(congure_reno_snd_t *c); + + /** + * @brief Callback to reset congestion window when entering + * fast recovery + * + * Defaults to + * + * ~~~~~~~~~~ {.c} + * c->ssthresh = max(c->mss * 2, c->cwnd / 2); + * c->cwnd = c->ssthresh + (3 * c->mss); + * ~~~~~~~~~~ + * + * when set to NULL + * + * @param[in] c The CongURE state object (callback context is at + * `c->super.ctx`) + */ + void (*fr_cwnd_dec)(congure_reno_snd_t *c); + + /** + * @brief Initial maximum segment size of the sender in intiator-defined + * units + * + * 1460 bytes for TCP over Ethernet + * (see [RFC 3390](https://tools.ietf.org/html/rfc3390)). + */ + unsigned init_mss; + + /** + * @brief Initial upper bound for initial window initiator-defined units + * + * 2190 bytes in classic TCP-Reno (3/4 of the assumed MSS for Ethernet, + * see [RFC 3390](https://tools.ietf.org/html/rfc3390)). + */ + congure_wnd_size_t cwnd_upper; + + /** + * @brief Initial lower bound for initial window initiator-defined units + * + * 1095 bytes in classic TCP-Reno (3/8 of the assumed MSS for Ethernet + * see [RFC 3390](https://tools.ietf.org/html/rfc3390)). + */ + congure_wnd_size_t cwnd_lower; + + /** + * @brief Initial slow-start threshold in initiator-defined units + */ + congure_wnd_size_t init_ssthresh; + + /** + * @brief Threshold for duplicate ACKs to go into Fast Retransmit + */ + uint8_t frthresh; +} congure_reno_snd_consts_t; + +/** + * @brief State object for CongURE Reno + * + * @extends congure_snd_t + */ +struct congure_reno_snd { + congure_snd_t super; /**< see @ref congure_snd_t */ + + /** + * @brief Constants + */ + const congure_reno_snd_consts_t *consts; + uint32_t last_ack; /**< ID of the last ACK reported */ + /** + * @brief Maximum segment size of the sender in caller-defined units + */ + congure_wnd_size_t mss; + congure_wnd_size_t ssthresh; /**< Slow-start threshold */ + /** + * @brief Sum of caller-defined units of message sizes of all messages + * that are yet not ack'd or declared lost + */ + uint16_t in_flight_size; + uint8_t dup_acks; /**< Number of duplicate ACKs reported */ +}; + +/** + * @brief Set-up @ref sys_congure_reno driver and constants. + * + * @pre Module `congure_reno` is compiled in (note: `congure_reno_methods` can + * also compile this module to enable @ref sys_congure_reno_methods, but + * not activate the module `congure_reno`) + * + * @param[in] c The @ref sys_congure_reno state object + * @param[in] consts The constants to use + */ +void congure_reno_snd_setup(congure_reno_snd_t *c, + const congure_reno_snd_consts_t *consts); + +/** + * @defgroup sys_congure_reno_methods The send driver methods for CongURE TCP Reno + * @ingroup sys_congure_reno + * + * Many other congestion control mechanisms are just adaptations of TCP Reno, + * so this makes the methods of @ref sys_congure_reno available to other + * @ref sys_congure modules. Use module `congure_reno_methods` to only compile + * in these modules, but not the driver for `congure_reno_snd_t` or + * @ref congure_reno_snd_setup(). + * + * @{ + */ +/** + * @brief Set sender maximum segment size. + * + * @attention This resets congure_reno_snd_t::cwnd to the new initial window + * size based on @p mss. So use with care. + * + * @param[in] c A CongURE state object + * @param[in] mss Maximum segment size of the sender in caller-defined units + */ +void congure_reno_set_mss(congure_reno_snd_t *c, congure_wnd_size_t mss); + +/** + * @brief Use to override congure_snd_driver_t::init + * + * @param[in,out] c The CongURE object to initialize. + * @param[in] ctx Context for callbacks specific to the congestion + * control (such as a TCP PCB). May be NULL. + */ +void congure_reno_snd_init(congure_snd_t *c, void *ctx); + +/** + * @brief Use to override congure_snd_driver_t::inter_msg_interval + * + * @param[in,out] c The CongURE object to initialize. + * @param[in] msg_size Size of the next message to sent in caller-defined + * unit. + * + * @return Always -1. + */ +int32_t congure_reno_snd_inter_msg_interval(congure_snd_t *c, + unsigned msg_size); + +/** + * @brief Use to override congure_snd_driver_t::report_msg_sent + * + * @param[in] c The CongURE state object. + * @param[in] msg_size Size of the message in caller-defined unit. + */ +void congure_reno_snd_report_msg_sent(congure_snd_t *c, unsigned msg_size); + +/** + * @brief Use to override congure_snd_driver_t::report_msg_discarded + * + * @param[in] c The CongURE state object. + * @param[in] msg_size Size of the discarded message in caller-defined + * unit. + */ +void congure_reno_snd_report_msg_discarded(congure_snd_t *c, unsigned msg_size); + +/** + * @brief Use to override congure_snd_driver_t::report_msgs_timeout + * + * @param[in] c The CongURE state object. + * @param[in] msgs A collection of messages that are known to be lost. + * The list must not be changed by the method. + */ +void congure_reno_snd_report_msgs_timeout(congure_snd_t *c, + congure_snd_msg_t *msgs); + +/** + * @brief Use to override congure_snd_driver_t::report_msgs_lost + * + * @param[in] c The CongURE state object. + * @param[in] msgs A collection of messages for which the ACK timed + * out. The list must not be changed by the method. + */ +void congure_reno_snd_report_msgs_lost(congure_snd_t *c, + congure_snd_msg_t *msgs); + +/** + * @brief Use to override congure_snd_driver_t::report_msg_acked + * + * @param[in] c The CongURE state object. + * @param[in] msg The ACK'd message. + * @param[in] ack The received ACK. + */ +void congure_reno_snd_report_msg_acked(congure_snd_t *c, congure_snd_msg_t *msg, + congure_snd_ack_t *ack); + +/** + * @brief Use to override congure_snd_driver_t::report_ecn_ce + * + * @param[in] c The CongURE state object. + * @param[in] time Timestamp in milliseconds of the message the CE + * event occurred for was sent. + */ +void congure_reno_snd_report_ecn_ce(congure_snd_t *c, ztimer_now_t time); +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* CONGURE_RENO_H */ +/** @} */ diff --git a/tests/congure_reno/app.config.test b/tests/congure_reno/app.config.test new file mode 100644 index 0000000000..ff27b641f7 --- /dev/null +++ b/tests/congure_reno/app.config.test @@ -0,0 +1,10 @@ +CONFIG_MODULE_CONGURE=y +CONFIG_MODULE_CONGURE_RENO=y +CONFIG_MODULE_CONGURE_TEST=y +CONFIG_MODULE_FMT=y +CONFIG_MODULE_SEQ=y +CONFIG_MODULE_SHELL=y +CONFIG_MODULE_SHELL_COMMANDS=y + +CONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE=6 +CONFIG_SHELL_NO_ECHO=y