mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-01-18 12:52:44 +01:00
Merge pull request #15953 from miri64/congure/feat/congure_reno
congure_reno: initial import of TCP Reno congestion control
This commit is contained in:
commit
c89f6bf115
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
14
sys/congure/reno/Kconfig
Normal file
14
sys/congure/reno/Kconfig
Normal file
@ -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().
|
3
sys/congure/reno/Makefile
Normal file
3
sys/congure/reno/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
MODULE := congure_reno
|
||||
|
||||
include $(RIOTBASE)/Makefile.base
|
36
sys/congure/reno/congure_reno.c
Normal file
36
sys/congure/reno/congure_reno.c
Normal file
@ -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 <m.lenders@fu-berlin.de>
|
||||
*/
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
/** @} */
|
3
sys/congure/reno/methods/Makefile
Normal file
3
sys/congure/reno/methods/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
MODULE := congure_reno_methods
|
||||
|
||||
include $(RIOTBASE)/Makefile.base
|
248
sys/congure/reno/methods/congure_reno_methods.c
Normal file
248
sys/congure/reno/methods/congure_reno_methods.c
Normal file
@ -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 <m.lenders@fu-berlin.de>
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
/** @} */
|
312
sys/include/congure/reno.h
Normal file
312
sys/include/congure/reno.h
Normal file
@ -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 <m.lenders@fu-berlin.de>
|
||||
*/
|
||||
#ifndef CONGURE_RENO_H
|
||||
#define CONGURE_RENO_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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 */
|
||||
/** @} */
|
24
tests/congure_reno/Makefile
Normal file
24
tests/congure_reno/Makefile
Normal file
@ -0,0 +1,24 @@
|
||||
include ../Makefile.tests_common
|
||||
|
||||
USEMODULE += congure_reno
|
||||
USEMODULE += congure_test
|
||||
USEMODULE += fmt
|
||||
USEMODULE += shell
|
||||
USEMODULE += shell_commands
|
||||
|
||||
INCLUDES += -I$(CURDIR)
|
||||
|
||||
# Use a terminal that does not introduce extra characters into the stream.
|
||||
RIOT_TERMINAL ?= socat
|
||||
|
||||
CFLAGS += -DSTDIO_UART_RX_BUFSIZE=512 # Adapt to SHELL_BUFSIZE in app
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
||||
|
||||
ifndef CONFIG_SHELL_NO_ECHO
|
||||
CFLAGS += -DCONFIG_SHELL_NO_ECHO=1
|
||||
endif
|
||||
|
||||
ifndef CONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE
|
||||
CFLAGS += -DCONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE=6
|
||||
endif
|
9
tests/congure_reno/Makefile.ci
Normal file
9
tests/congure_reno/Makefile.ci
Normal file
@ -0,0 +1,9 @@
|
||||
BOARD_INSUFFICIENT_MEMORY := \
|
||||
arduino-duemilanove \
|
||||
arduino-leonardo \
|
||||
arduino-nano \
|
||||
arduino-uno \
|
||||
atmega328p \
|
||||
atmega328p-xplained-mini \
|
||||
nucleo-l011k4 \
|
||||
#
|
30
tests/congure_reno/README.md
Normal file
30
tests/congure_reno/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
Tests for the CongURE TCP Reno implementation
|
||||
=============================================
|
||||
|
||||
This test tests the `congure_reno` implementation.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
The test requires an up-to-date version of `riotctrl` with `rapidjson` support:
|
||||
|
||||
```console
|
||||
$ pip install --upgrade riotctrl[rapidjson]
|
||||
```
|
||||
|
||||
Then simply run the application using:
|
||||
|
||||
```console
|
||||
$ BOARD="<board>" make flash test
|
||||
```
|
||||
|
||||
It can also executed with pytest:
|
||||
|
||||
```console
|
||||
$ pytest tests/01-run.py
|
||||
```
|
||||
|
||||
Expected result
|
||||
---------------
|
||||
|
||||
The application's test script passes without error code.
|
4
tests/congure_reno/app.config
Normal file
4
tests/congure_reno/app.config
Normal file
@ -0,0 +1,4 @@
|
||||
CONFIG_KCONFIG_USEMODULE_CONGURE_TEST=y
|
||||
CONFIG_KCONFIG_USEMODULE_SHELL=y
|
||||
CONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE=6
|
||||
CONFIG_SHELL_NO_ECHO=y
|
10
tests/congure_reno/app.config.test
Normal file
10
tests/congure_reno/app.config.test
Normal file
@ -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
|
102
tests/congure_reno/congure_impl.c
Normal file
102
tests/congure_reno/congure_impl.c
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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 <m.lenders@fu-berlin.de>
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "kernel_defines.h"
|
||||
|
||||
#include "congure_impl.h"
|
||||
|
||||
static unsigned _fr_calls;
|
||||
static bool _same_wnd_adv_res;
|
||||
|
||||
static void _fr(congure_reno_snd_t *c);
|
||||
static bool _same_wnd_adv(congure_reno_snd_t *c, congure_snd_ack_t *ack);
|
||||
static void _ss_cwnd_inc(congure_reno_snd_t *c);
|
||||
static void _ca_cwnd_inc(congure_reno_snd_t *c);
|
||||
static void _fr_cwnd_dec(congure_reno_snd_t *c);
|
||||
|
||||
static const congure_reno_snd_consts_t _consts[] = {
|
||||
{
|
||||
.fr = _fr,
|
||||
.same_wnd_adv = _same_wnd_adv,
|
||||
.init_mss = 1460,
|
||||
.cwnd_lower = 1095,
|
||||
.cwnd_upper = 2190,
|
||||
.init_ssthresh = CONGURE_WND_SIZE_MAX,
|
||||
.frthresh = 3,
|
||||
},
|
||||
{
|
||||
.fr = _fr,
|
||||
.same_wnd_adv = _same_wnd_adv,
|
||||
.ss_cwnd_inc = _ss_cwnd_inc,
|
||||
.ca_cwnd_inc = _ca_cwnd_inc,
|
||||
.fr_cwnd_dec = _fr_cwnd_dec,
|
||||
.init_mss = 1460,
|
||||
.cwnd_lower = 1095,
|
||||
.cwnd_upper = 2190,
|
||||
.init_ssthresh = CONGURE_WND_SIZE_MAX,
|
||||
.frthresh = 3,
|
||||
},
|
||||
};
|
||||
|
||||
int congure_test_snd_setup(congure_test_snd_t *c, unsigned id)
|
||||
{
|
||||
if (id >= ARRAY_SIZE(_consts)) {
|
||||
return -1;
|
||||
}
|
||||
_fr_calls = 0;
|
||||
congure_reno_snd_setup(c, &_consts[id]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned congure_reno_test_get_fr_calls(void)
|
||||
{
|
||||
return _fr_calls;
|
||||
}
|
||||
|
||||
void congure_reno_test_set_same_wnd_adv_res(bool value)
|
||||
{
|
||||
_same_wnd_adv_res = value;
|
||||
}
|
||||
|
||||
static void _fr(congure_reno_snd_t *c)
|
||||
{
|
||||
(void)c;
|
||||
_fr_calls++;
|
||||
}
|
||||
|
||||
static bool _same_wnd_adv(congure_reno_snd_t *c, congure_snd_ack_t *ack)
|
||||
{
|
||||
(void)c;
|
||||
(void)ack;
|
||||
return _same_wnd_adv_res;
|
||||
}
|
||||
|
||||
static void _ss_cwnd_inc(congure_reno_snd_t *c)
|
||||
{
|
||||
c->super.cwnd += 1337;
|
||||
}
|
||||
|
||||
static void _ca_cwnd_inc(congure_reno_snd_t *c)
|
||||
{
|
||||
c->super.cwnd += 42;
|
||||
}
|
||||
|
||||
static void _fr_cwnd_dec(congure_reno_snd_t *c)
|
||||
{
|
||||
c->super.cwnd /= 8;
|
||||
}
|
||||
|
||||
/** @} */
|
36
tests/congure_reno/congure_impl.h
Normal file
36
tests/congure_reno/congure_impl.h
Normal file
@ -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 <m.lenders@fu-berlin.de>
|
||||
*/
|
||||
#ifndef CONGURE_IMPL_H
|
||||
#define CONGURE_IMPL_H
|
||||
|
||||
#include "congure/reno.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef congure_reno_snd_t congure_test_snd_t;
|
||||
|
||||
int congure_test_snd_setup(congure_test_snd_t *c, unsigned id);
|
||||
unsigned congure_reno_test_get_fr_calls(void);
|
||||
void congure_reno_test_set_same_wnd_adv_res(bool value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CONGURE_IMPL_H */
|
||||
/** @} */
|
187
tests/congure_reno/main.c
Normal file
187
tests/congure_reno/main.c
Normal file
@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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 S. Lenders <m.lenders@fu-berlin.de>
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "clist.h"
|
||||
#include "congure/test.h"
|
||||
#include "fmt.h"
|
||||
#include "shell.h"
|
||||
|
||||
#include "congure_impl.h"
|
||||
|
||||
#define SHELL_BUFSIZE 512U
|
||||
|
||||
static char _line_buf[SHELL_BUFSIZE];
|
||||
|
||||
static int _json_statham(int argc, char **argv);
|
||||
static int _set_mss(int argc, char **argv);
|
||||
static int _set_cwnd(int argc, char **argv);
|
||||
static int _set_ssthresh(int argc, char **argv);
|
||||
static int _get_fr_calls(int argc, char **argv);
|
||||
static int _set_same_wnd_adv_res(int argc, char **argv);
|
||||
|
||||
static congure_reno_snd_t _congure_state;
|
||||
static const shell_command_t shell_commands[] = {
|
||||
{ "state", "Prints current CongURE state object as JSON", _json_statham },
|
||||
{ "set_cwnd", "Set cwnd member for CongURE state object", _set_cwnd },
|
||||
{ "set_mss", "Set new MSS for CongURE state object", _set_mss },
|
||||
{ "set_ssthresh", "Set ssthresh member for CongURE state object",
|
||||
_set_ssthresh },
|
||||
{ "get_ff_calls",
|
||||
"Get the number of calls to fast_retransmit callback of CongURE state "
|
||||
"object", _get_fr_calls },
|
||||
{ "set_same_wnd_adv",
|
||||
"Set the result for the same_window_advertised callback of CongURE state "
|
||||
"object", _set_same_wnd_adv_res },
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
shell_run(shell_commands, _line_buf, SHELL_BUFSIZE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
congure_test_snd_t *congure_test_get_state(void)
|
||||
{
|
||||
return &_congure_state;
|
||||
}
|
||||
|
||||
#define PRINT_FIELD_PTR(obj_ptr, field) \
|
||||
print_str("\"" #field "\":\"0x"); \
|
||||
print_u32_hex((intptr_t)((obj_ptr)->field)); \
|
||||
print_str("\",")
|
||||
|
||||
#define PRINT_FIELD_UINT(obj, field) \
|
||||
print_str("\"" #field "\":"); \
|
||||
print_u32_dec((obj).field); \
|
||||
print_str(",")
|
||||
|
||||
static void _print_congure_reno_consts(const congure_reno_snd_consts_t *consts)
|
||||
{
|
||||
print_str("\"consts\":");
|
||||
|
||||
if (consts) {
|
||||
print_str("{");
|
||||
PRINT_FIELD_PTR(consts, fr);
|
||||
PRINT_FIELD_PTR(consts, same_wnd_adv);
|
||||
PRINT_FIELD_PTR(consts, ss_cwnd_inc);
|
||||
PRINT_FIELD_PTR(consts, ca_cwnd_inc);
|
||||
PRINT_FIELD_PTR(consts, fr_cwnd_dec);
|
||||
PRINT_FIELD_UINT(*consts, init_mss);
|
||||
PRINT_FIELD_UINT(*consts, cwnd_upper);
|
||||
PRINT_FIELD_UINT(*consts, cwnd_lower);
|
||||
PRINT_FIELD_UINT(*consts, init_ssthresh);
|
||||
PRINT_FIELD_UINT(*consts, frthresh);
|
||||
print_str("},");
|
||||
}
|
||||
else {
|
||||
print_str("null,");
|
||||
}
|
||||
}
|
||||
|
||||
static int _json_statham(int argc, char **argv)
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
print_str("{");
|
||||
|
||||
PRINT_FIELD_UINT(_congure_state.super, cwnd);
|
||||
_print_congure_reno_consts(_congure_state.consts);
|
||||
PRINT_FIELD_UINT(_congure_state, mss);
|
||||
PRINT_FIELD_UINT(_congure_state, last_ack);
|
||||
PRINT_FIELD_UINT(_congure_state, ssthresh);
|
||||
PRINT_FIELD_UINT(_congure_state, in_flight_size);
|
||||
PRINT_FIELD_UINT(_congure_state, dup_acks);
|
||||
|
||||
print_str("}\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _set_mss(int argc, char **argv)
|
||||
{
|
||||
uint32_t tmp;
|
||||
|
||||
if (argc < 2) {
|
||||
print_str("{\"error\":\"`mss` argument expected\"}");
|
||||
return 1;
|
||||
}
|
||||
tmp = scn_u32_dec(argv[1], strlen(argv[1]));
|
||||
if (tmp > CONGURE_WND_SIZE_MAX) {
|
||||
print_str("{\"error\":\"`mss` not 16 bit wide\"}\n");
|
||||
}
|
||||
congure_reno_set_mss(&_congure_state, (congure_wnd_size_t)tmp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _set_cwnd(int argc, char **argv)
|
||||
{
|
||||
uint32_t tmp;
|
||||
|
||||
if (argc < 2) {
|
||||
print_str("{\"error\":\"`cwnd` argument expected\"}");
|
||||
return 1;
|
||||
}
|
||||
tmp = scn_u32_dec(argv[1], strlen(argv[1]));
|
||||
if (tmp > CONGURE_WND_SIZE_MAX) {
|
||||
print_str("{\"error\":\"`cwnd` not 16 bit wide\"}\n");
|
||||
}
|
||||
_congure_state.super.cwnd = (congure_wnd_size_t)tmp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _set_ssthresh(int argc, char **argv)
|
||||
{
|
||||
uint32_t tmp;
|
||||
|
||||
if (argc < 2) {
|
||||
print_str("{\"error\":\"`ssthresh` argument expected\"}");
|
||||
return 1;
|
||||
}
|
||||
tmp = scn_u32_dec(argv[1], strlen(argv[1]));
|
||||
if (tmp > CONGURE_WND_SIZE_MAX) {
|
||||
print_str("{\"error\":\"`ssthresh` not 16 bit wide\"}\n");
|
||||
}
|
||||
_congure_state.ssthresh = (congure_wnd_size_t)tmp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _get_fr_calls(int argc, char **argv)
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
print_str("{\"fr_calls\":");
|
||||
print_u32_dec(congure_reno_test_get_fr_calls());
|
||||
print_str("}\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _set_same_wnd_adv_res(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
print_str("{\"error\":\"`value` argument expected\"}");
|
||||
return 1;
|
||||
}
|
||||
congure_reno_test_set_same_wnd_adv_res(
|
||||
(bool)scn_u32_dec(argv[1], strlen(argv[1]))
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @} */
|
835
tests/congure_reno/tests/01-run.py
Executable file
835
tests/congure_reno/tests/01-run.py
Executable file
@ -0,0 +1,835 @@
|
||||
#! /usr/bin/env python3
|
||||
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from riotctrl.ctrl import RIOTCtrl
|
||||
from riotctrl.shell.json import RapidJSONShellInteractionParser, rapidjson
|
||||
|
||||
from riotctrl_shell.congure_test import CongureTest
|
||||
|
||||
|
||||
class TestCongUREBase(unittest.TestCase):
|
||||
# pylint: disable=too-many-public-methods
|
||||
# it's just one more ...
|
||||
DEBUG = False
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.ctrl = RIOTCtrl()
|
||||
cls.ctrl.reset()
|
||||
cls.ctrl.start_term()
|
||||
if cls.DEBUG:
|
||||
cls.ctrl.term.logfile = sys.stdout
|
||||
cls.shell = CongureTest(cls.ctrl)
|
||||
cls.json_parser = RapidJSONShellInteractionParser()
|
||||
cls.json_parser.set_parser_args(
|
||||
parse_mode=rapidjson.PM_TRAILING_COMMAS
|
||||
)
|
||||
cls.logger = logging.getLogger(cls.__name__)
|
||||
if cls.DEBUG:
|
||||
cls.logger.setLevel(logging.DEBUG)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.ctrl.stop_term()
|
||||
|
||||
def setUp(self):
|
||||
self.shell.clear()
|
||||
|
||||
def tearDown(self):
|
||||
self.shell.msgs_reset()
|
||||
|
||||
def _parse(self, res):
|
||||
self.logger.debug(res)
|
||||
if res.strip():
|
||||
return self.json_parser.parse(res)
|
||||
return None
|
||||
|
||||
def exec_cmd(self, cmd, timeout=-1, async_=False):
|
||||
res = self.shell.cmd(cmd, timeout, async_)
|
||||
return self._parse(res)
|
||||
|
||||
def assertSlowStart(self, state):
|
||||
# pylint: disable=invalid-name
|
||||
# trying to be in line with `unittest`
|
||||
"""
|
||||
> The slow start algorithm is used when cwnd < ssthresh, while the
|
||||
> congestion avoidance algorithm is used when cwnd > ssthresh. When
|
||||
> cwnd and ssthresh are equal, the sender may use either slow start or
|
||||
> congestion avoidance.
|
||||
"""
|
||||
self.assertLess(state['cwnd'], state['ssthresh'])
|
||||
|
||||
def assertCongestionAvoidance(self, state):
|
||||
# pylint: disable=invalid-name
|
||||
# trying to be in line with `unittest`
|
||||
"""
|
||||
> The slow start algorithm is used when cwnd < ssthresh, while the
|
||||
> congestion avoidance algorithm is used when cwnd > ssthresh. When
|
||||
> cwnd and ssthresh are equal, the sender may use either slow start or
|
||||
> congestion avoidance.
|
||||
"""
|
||||
self.assertGreaterEqual(state['cwnd'], state['ssthresh'])
|
||||
|
||||
def assertInFastRetransmit(self, state):
|
||||
# pylint: disable=invalid-name
|
||||
# trying to be in line with `unittest`
|
||||
"""
|
||||
> The TCP sender SHOULD use the "fast retransmit" algorithm to detect
|
||||
> and repair loss, based on incoming duplicate ACKs. The fast
|
||||
> retransmit algorithm uses the arrival of 3 duplicate ACKs [...] as
|
||||
> an indication that a segment has been lost.
|
||||
"""
|
||||
self.assertGreaterEqual(state['dup_acks'], state['consts']['frthresh'])
|
||||
|
||||
def assertNotInFastRetransmit(self, state):
|
||||
# pylint: disable=invalid-name
|
||||
# trying to be in line with `unittest`
|
||||
"""Reverse of self.assertInFastRetransmit()"""
|
||||
self.assertLess(state['dup_acks'], state['consts']['frthresh'])
|
||||
|
||||
def get_ff_calls(self):
|
||||
res = self.exec_cmd('get_ff_calls')
|
||||
return res['fr_calls']
|
||||
|
||||
def set_same_wnd_adv(self, value):
|
||||
self.exec_cmd('set_same_wnd_adv {value:d}'.format(value=value))
|
||||
|
||||
def set_mss(self, mss):
|
||||
self.exec_cmd('set_mss {mss}'.format(mss=mss))
|
||||
|
||||
def set_cwnd(self, cwnd):
|
||||
self.exec_cmd('set_cwnd {cwnd}'.format(cwnd=cwnd))
|
||||
|
||||
def set_ssthresh(self, ssthresh):
|
||||
self.exec_cmd('set_ssthresh {ssthresh}'.format(ssthresh=ssthresh))
|
||||
|
||||
def cong_state(self):
|
||||
return self.exec_cmd('state')
|
||||
|
||||
def cong_init(self, ctx=0):
|
||||
res = self.shell.init(ctx)
|
||||
return self._parse(res)
|
||||
|
||||
def cong_report_msg_sent(self, msg_size):
|
||||
res = self.shell.report_msg_sent(msg_size)
|
||||
return self._parse(res)
|
||||
|
||||
def cong_report_msg_discarded(self, msg_size):
|
||||
res = self.shell.report_msg_discarded(msg_size)
|
||||
return self._parse(res)
|
||||
|
||||
def cong_report_msgs_timeout(self, msgs):
|
||||
res = self.shell.report_msgs_timeout(msgs)
|
||||
return self._parse(res)
|
||||
|
||||
def cong_report_msgs_lost(self, msgs):
|
||||
res = self.shell.report_msgs_lost(msgs)
|
||||
return self._parse(res)
|
||||
|
||||
def cong_report_msg_acked(self, msg, ack):
|
||||
res = self.shell.report_msg_acked(msg, ack)
|
||||
return self._parse(res)
|
||||
|
||||
def cong_report_ecn_ce(self, time):
|
||||
res = self.shell.report_ecn_ce(time)
|
||||
return self._parse(res)
|
||||
|
||||
def _send_msg_and_recv_ack(self, msg_size, msg_resends=0,
|
||||
ack_id=15, ack_size=None, ack_clean=True):
|
||||
# pylint: disable=too-many-arguments
|
||||
# already reduced number of arguments, cong_report_msg_acked would
|
||||
# need...
|
||||
if ack_size is None:
|
||||
# set ack_size to arbitrary value
|
||||
ack_size = msg_size
|
||||
res = self.cong_report_msg_sent(msg_size=msg_size)
|
||||
self.assertIn('success', res)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], msg_size)
|
||||
res = self.cong_report_msg_acked(
|
||||
msg={'send_time': 1000, 'size': msg_size, 'resends': msg_resends},
|
||||
ack={'recv_time': 1100, 'id': ack_id, 'size': ack_size,
|
||||
'clean': ack_clean, 'wnd': 1234, 'delay': 0},
|
||||
)
|
||||
self.assertIn('success', res)
|
||||
# this method is reused a lot, so reset internal message buffer of
|
||||
# `congure_test`
|
||||
res = self._parse(self.shell.msgs_reset())
|
||||
self.assertIn('success', res)
|
||||
|
||||
|
||||
class TestCongURERenoWithoutSetup(TestCongUREBase):
|
||||
def test_no_setup(self):
|
||||
state = self.exec_cmd('state')
|
||||
self.assertEqual(state, {
|
||||
'cwnd': 0,
|
||||
'consts': None,
|
||||
'mss': 0,
|
||||
'last_ack': 0,
|
||||
'ssthresh': 0,
|
||||
'in_flight_size': 0,
|
||||
'dup_acks': 0,
|
||||
})
|
||||
|
||||
|
||||
class TestCongURERenoDefaultInitTests(TestCongUREBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
res = self.shell.setup(0)
|
||||
self.assertIn('success', res)
|
||||
|
||||
def test_setup(self):
|
||||
state = self.cong_state()
|
||||
self.assertIsNotNone(state['consts'])
|
||||
# fast_retransmit and same_window_advertised need to be set to a
|
||||
# function pointer
|
||||
self.assertNotEqual(int(state['consts']['fr'], base=16), 0)
|
||||
self.assertNotEqual(int(state['consts']['same_wnd_adv'], base=16), 0)
|
||||
# ss_cwnd_inc, ca_cwnd_inc, fr_cwnd_dec are optional and setup 0 need
|
||||
# to be set to a function pointer
|
||||
self.assertEqual(int(state['consts']['ss_cwnd_inc'], base=16), 0)
|
||||
self.assertEqual(int(state['consts']['ca_cwnd_inc'], base=16), 0)
|
||||
self.assertEqual(int(state['consts']['fr_cwnd_dec'], base=16), 0)
|
||||
self.assertEqual(state['consts']['init_mss'], 1460)
|
||||
self.assertEqual(state['consts']['cwnd_lower'], 1095)
|
||||
self.assertEqual(state['consts']['cwnd_upper'], 2190)
|
||||
self.assertEqual(state['consts']['init_ssthresh'], 0xffff)
|
||||
self.assertEqual(state['consts']['frthresh'], 3)
|
||||
|
||||
def test_init(self):
|
||||
"""
|
||||
https://tools.ietf.org/html/rfc5681#section-3.1
|
||||
|
||||
> IW, the initial value of cwnd, MUST be set using the following
|
||||
> guidelines as an upper bound.
|
||||
>
|
||||
> If SMSS > 2190 bytes:
|
||||
> IW = 2 * SMSS bytes and MUST NOT be more than 2 segments
|
||||
> If (SMSS > 1095 bytes) and (SMSS <= 2190 bytes):
|
||||
> IW = 3 * SMSS bytes and MUST NOT be more than 3 segments
|
||||
> if SMSS <= 1095 bytes:
|
||||
> IW = 4 * SMSS bytes and MUST NOT be more than 4 segments
|
||||
"""
|
||||
res = self.cong_init()
|
||||
self.assertIn('success', res)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['consts']['init_mss'], state['mss'])
|
||||
# (SMSS > 1095 bytes)
|
||||
self.assertGreater(state['mss'], state['consts']['cwnd_lower'])
|
||||
# (SMSS <= 2190 bytes)
|
||||
self.assertLessEqual(state['mss'], state['consts']['cwnd_upper'])
|
||||
# as such, IW = 3 * SMSS bytes
|
||||
self.assertEqual(state['cwnd'], 3 * state['mss'])
|
||||
# We start with slow start
|
||||
self.assertSlowStart(state)
|
||||
self.assertNotInFastRetransmit(state)
|
||||
|
||||
|
||||
class TestCongURERenoDefault(TestCongUREBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
res = self.shell.setup(0)
|
||||
self.assertIn('success', res)
|
||||
res = self.cong_init()
|
||||
self.assertIn('success', res)
|
||||
|
||||
def test_mss_2200(self):
|
||||
"""
|
||||
https://tools.ietf.org/html/rfc5681#section-3.1
|
||||
|
||||
> IW, the initial value of cwnd, MUST be set using the following
|
||||
> guidelines as an upper bound.
|
||||
>
|
||||
> If SMSS > 2190 bytes:
|
||||
> IW = 2 * SMSS bytes and MUST NOT be more than 2 segments
|
||||
> If (SMSS > 1095 bytes) and (SMSS <= 2190 bytes):
|
||||
> IW = 3 * SMSS bytes and MUST NOT be more than 3 segments
|
||||
> if SMSS <= 1095 bytes:
|
||||
> IW = 4 * SMSS bytes and MUST NOT be more than 4 segments
|
||||
"""
|
||||
self.set_mss(2200)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(2200, state['mss'])
|
||||
# (SMSS > 2190 bytes)
|
||||
self.assertGreater(state['mss'], state['consts']['cwnd_upper'])
|
||||
# (SMSS > 1095 bytes)
|
||||
self.assertGreater(state['mss'], state['consts']['cwnd_lower'])
|
||||
# as such, IW = 2 * SMSS bytes
|
||||
self.assertEqual(state['cwnd'], 2 * state['mss'])
|
||||
# We start with slow start
|
||||
self.assertSlowStart(state)
|
||||
self.assertNotInFastRetransmit(state)
|
||||
|
||||
def test_mss_1095(self):
|
||||
"""
|
||||
https://tools.ietf.org/html/rfc5681#section-3.1
|
||||
|
||||
> IW, the initial value of cwnd, MUST be set using the following
|
||||
> guidelines as an upper bound.
|
||||
>
|
||||
> If SMSS > 2190 bytes:
|
||||
> IW = 2 * SMSS bytes and MUST NOT be more than 2 segments
|
||||
> If (SMSS > 1095 bytes) and (SMSS <= 2190 bytes):
|
||||
> IW = 3 * SMSS bytes and MUST NOT be more than 3 segments
|
||||
> if SMSS <= 1095 bytes:
|
||||
> IW = 4 * SMSS bytes and MUST NOT be more than 4 segments
|
||||
"""
|
||||
self.set_mss(1095)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(1095, state['mss'])
|
||||
# (SMSS < 2190 bytes)
|
||||
self.assertLess(state['mss'], state['consts']['cwnd_upper'])
|
||||
# (SMSS < 1095 bytes)
|
||||
self.assertLessEqual(state['mss'], state['consts']['cwnd_lower'])
|
||||
# as such, IW = 4 * SMSS bytes
|
||||
self.assertEqual(state['cwnd'], 4 * state['mss'])
|
||||
# We start with slow start
|
||||
self.assertSlowStart(state)
|
||||
self.assertNotInFastRetransmit(state)
|
||||
|
||||
def test_slow_start_increase_small_N(self):
|
||||
# pylint: disable=invalid-name
|
||||
# name chosen to be in line with RFC
|
||||
"""
|
||||
https://tools.ietf.org/html/rfc5681#section-3.1
|
||||
|
||||
> During slow start, a TCP increments cwnd by at most SMSS bytes for
|
||||
> each ACK received that cumulatively acknowledges new data. Slow
|
||||
> start ends when cwnd exceeds ssthresh (or, optionally, when it
|
||||
> reaches it, as noted above) or when congestion is observed. While
|
||||
> traditionally TCP implementations have increased cwnd by precisely
|
||||
> SMSS bytes upon receipt of an ACK covering new data, we RECOMMEND
|
||||
> that TCP implementations increase cwnd, per:
|
||||
>
|
||||
> cwnd += min (N, SMSS) (2)
|
||||
>
|
||||
> where N is the number of previously unacknowledged bytes acknowledged
|
||||
> in the incoming ACK.
|
||||
"""
|
||||
state = self.cong_state()
|
||||
init_cwnd = state['cwnd']
|
||||
init_ssthresh = state['ssthresh']
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
# pylint: disable=invalid-name
|
||||
# name chosen to be in line with RFC
|
||||
N = 42
|
||||
self._send_msg_and_recv_ack(N)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['cwnd'], init_cwnd + N)
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
self.assertEqual(state['ssthresh'], init_ssthresh)
|
||||
self.assertNotInFastRetransmit(state)
|
||||
|
||||
def test_slow_start_increase_large_N(self):
|
||||
# pylint: disable=invalid-name
|
||||
# name chosen to be in line with RFC
|
||||
"""
|
||||
Same as test_slow_start_increase_small_N(), but with N larger than SMSS
|
||||
"""
|
||||
state = self.cong_state()
|
||||
init_cwnd = state['cwnd']
|
||||
init_mss = state['mss']
|
||||
init_ssthresh = state['ssthresh']
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
# pylint: disable=invalid-name
|
||||
# name chosen to be in line with RFC
|
||||
# set N to larger than SMSS
|
||||
N = state['mss'] + 1337
|
||||
self._send_msg_and_recv_ack(N)
|
||||
state = self.cong_state()
|
||||
# MSS did not change
|
||||
self.assertEqual(state['mss'], init_mss)
|
||||
self.assertEqual(state['cwnd'], init_cwnd + state['mss'])
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
self.assertEqual(state['ssthresh'], init_ssthresh)
|
||||
self.assertNotInFastRetransmit(state)
|
||||
|
||||
def test_congestion_avoidance_increase(self):
|
||||
"""
|
||||
During congestion avoidance, cwnd is incremented by roughly 1 full-
|
||||
sized segment per round-trip time (RTT). Congestion avoidance
|
||||
continues until congestion is detected. The basic guidelines for
|
||||
incrementing cwnd during congestion avoidance are:
|
||||
|
||||
* MAY increment cwnd by SMSS bytes
|
||||
|
||||
* SHOULD increment cwnd per equation (2) once per RTT
|
||||
|
||||
* MUST NOT increment cwnd by more than SMSS bytes
|
||||
"""
|
||||
state = self.cong_state()
|
||||
init_cwnd = state['cwnd']
|
||||
init_mss = state['mss']
|
||||
init_ssthresh = init_cwnd - init_mss
|
||||
self.set_ssthresh(init_ssthresh)
|
||||
state = self.cong_state()
|
||||
self.assertCongestionAvoidance(state)
|
||||
# pylint: disable=invalid-name
|
||||
# name chosen to be in line with RFC
|
||||
N = 42
|
||||
self._send_msg_and_recv_ack(N)
|
||||
state = self.cong_state()
|
||||
# cwnd was incremented
|
||||
self.assertGreater(state['cwnd'], init_cwnd)
|
||||
# cwnd was not incremented by more than SMSS bytes
|
||||
self.assertLessEqual(state['cwnd'], init_cwnd + init_mss)
|
||||
self.assertNotInFastRetransmit(state)
|
||||
|
||||
def _send_msg_and_timeout(self, msgs):
|
||||
state = self.cong_state()
|
||||
flight_size = state['in_flight_size']
|
||||
for msg in msgs:
|
||||
res = self.cong_report_msg_sent(msg_size=msg['size'])
|
||||
self.assertIn('success', res)
|
||||
flight_size += msg['size']
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], flight_size)
|
||||
res = self.cong_report_msgs_timeout(msgs)
|
||||
self.assertIn('success', res)
|
||||
|
||||
def test_reduce_ssthresh_small_flight_size(self):
|
||||
"""
|
||||
https://tools.ietf.org/html/rfc5681#section-3.1
|
||||
|
||||
> When a TCP sender detects segment loss using the retransmission timer
|
||||
> and the given segment has not yet been resent by way of the
|
||||
> retransmission timer, the value of ssthresh MUST be set to no more
|
||||
> than the value given in equation (4):
|
||||
>
|
||||
> ssthresh = max (FlightSize / 2, 2*SMSS) (4)
|
||||
>
|
||||
> where, as discussed above, FlightSize is the amount of outstanding
|
||||
> data in the network.
|
||||
"""
|
||||
state = self.cong_state()
|
||||
init_cwnd = state['cwnd']
|
||||
init_mss = state['mss']
|
||||
init_ssthresh = state['ssthresh']
|
||||
self.assertNotEqual(init_cwnd, init_mss)
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
flight_size = 42
|
||||
self._send_msg_and_timeout([{'size': flight_size, 'send_time': 1000,
|
||||
'resends': 0}])
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
# MSS did not change
|
||||
self.assertEqual(state['mss'], init_mss)
|
||||
"""
|
||||
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).
|
||||
"""
|
||||
self.assertEqual(state['cwnd'], init_mss)
|
||||
# slow-start threshold changed
|
||||
self.assertNotEqual(state['ssthresh'], init_ssthresh)
|
||||
# and became slow-start threshold became 2*SMSS
|
||||
self.assertEqual(state['ssthresh'], init_mss * 2)
|
||||
# we should still be in slow start, as cwnd == SMSS and
|
||||
# ssthresh == 2 * SMSS
|
||||
self.assertSlowStart(state)
|
||||
self.assertNotInFastRetransmit(state)
|
||||
|
||||
def test_reduce_ssthresh_large_flight_size(self):
|
||||
"""
|
||||
Same as test_reduce_ssthresh_small_flight_size, but with flight size
|
||||
larger than 4 * SMSS
|
||||
"""
|
||||
state = self.cong_state()
|
||||
init_cwnd = state['cwnd']
|
||||
init_mss = state['mss']
|
||||
init_ssthresh = state['ssthresh']
|
||||
self.assertNotEqual(init_cwnd, init_mss)
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
flight_size = (4 * init_mss) + 543
|
||||
# increase congestion window large enough to send all those bytes
|
||||
self.set_cwnd(flight_size)
|
||||
self._send_msg_and_timeout([
|
||||
{'size': init_mss, 'send_time': 1000, 'resends': 0},
|
||||
{'size': init_mss, 'send_time': 1010, 'resends': 0},
|
||||
{'size': init_mss, 'send_time': 1020, 'resends': 0},
|
||||
{'size': init_mss, 'send_time': 1030, 'resends': 0},
|
||||
{'size': 543, 'send_time': 140, 'resends': 0},
|
||||
])
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
# MSS did not change
|
||||
self.assertEqual(state['mss'], init_mss)
|
||||
# cwnd became SMSS due to timeout
|
||||
self.assertEqual(state['cwnd'], init_mss)
|
||||
# slow-start threshold changed
|
||||
self.assertNotEqual(state['ssthresh'], init_ssthresh)
|
||||
# and became slow-start threshold became flight_size / 2
|
||||
self.assertEqual(state['ssthresh'], flight_size // 2)
|
||||
# we should still be in slow start, as cwnd == SMSS and
|
||||
# ssthresh == flight_size / 2 == ((2 * SMSS) + (543 / 2))
|
||||
self.assertSlowStart(state)
|
||||
self.assertNotInFastRetransmit(state)
|
||||
|
||||
def test_enter_fast_retransmit_check_a_not_true(self):
|
||||
"""
|
||||
Fast retransmit according to RFC 5681 when receiving a duplicate
|
||||
ACK is received (see RFC 5681, section 3.2).
|
||||
|
||||
> The TCP sender SHOULD use the "fast retransmit" algorithm to detect
|
||||
> and repair loss, based on incoming duplicate ACKs. The fast
|
||||
> retransmit algorithm uses the arrival of 3 duplicate ACKs [...]
|
||||
> as an indication that a segment has been lost.
|
||||
|
||||
A duplicate ACK according to
|
||||
https://tools.ietf.org/html/rfc5681#section-2 is defined as
|
||||
|
||||
> DUPLICATE ACKNOWLEDGMENT: An acknowledgment is considered a
|
||||
> "duplicate" in the following algorithms when (a) the receiver of
|
||||
> the ACK has outstanding data, (b) the incoming acknowledgment
|
||||
> carries no data, (c) the SYN and FIN bits are both off, (d) the
|
||||
> acknowledgment number is equal to the greatest acknowledgment
|
||||
> received on the given connection (TCP.UNA from [RFC793]) and (e)
|
||||
> the advertised window in the incoming acknowledgment equals the
|
||||
> advertised window in the last incoming acknowledgment.
|
||||
|
||||
This test checks if fast retransmit is NOT entered when condition (a)
|
||||
is not true but all others are
|
||||
"""
|
||||
state = self.cong_state()
|
||||
self.assertEqual(0, self.get_ff_calls())
|
||||
self.assertNotInFastRetransmit(state)
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
self._send_msg_and_recv_ack(42, ack_id=15, ack_size=0, ack_clean=True)
|
||||
# condition (a) is not fulfilled
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
# make condition (e) true
|
||||
self.set_same_wnd_adv(True)
|
||||
# condition (b) ack['size'] == 0, (c) ack['clean'] == True,
|
||||
# (d) ack['id'] == 15
|
||||
for _ in range(3):
|
||||
res = self.cong_report_msg_acked(
|
||||
msg={'send_time': 1000, 'size': 42, 'resends': 0},
|
||||
ack={'recv_time': 1100, 'id': 15, 'size': 0,
|
||||
'clean': True, 'wnd': 1234, 'delay': 0},
|
||||
)
|
||||
self.assertIn('success', res)
|
||||
self.assertEqual(0, self.get_ff_calls())
|
||||
self.assertNotInFastRetransmit(state)
|
||||
|
||||
def test_enter_fast_retransmit_check_b_not_true(self):
|
||||
"""
|
||||
See self.test_enter_fast_retransmit_check_a_not_true()
|
||||
This test checks if fast retransmit is NOT entered when condition (b)
|
||||
is not true but all others are
|
||||
"""
|
||||
state = self.cong_state()
|
||||
self.assertEqual(0, self.get_ff_calls())
|
||||
self.assertNotInFastRetransmit(state)
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
self._send_msg_and_recv_ack(42, ack_id=15, ack_size=0, ack_clean=True)
|
||||
# make condition (a) true
|
||||
self.cong_report_msg_sent(52)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], 52)
|
||||
# make condition (e) true
|
||||
self.set_same_wnd_adv(True)
|
||||
# condition (c) ack['clean'] == True, (d) ack['id'] == 15
|
||||
# condition (b) not true by setting ack['size'] > 0
|
||||
for _ in range(3):
|
||||
res = self.cong_report_msg_acked(
|
||||
msg={'send_time': 1000, 'size': 42, 'resends': 0},
|
||||
ack={'recv_time': 1100, 'id': 15, 'size': 643,
|
||||
'clean': True, 'wnd': 1234, 'delay': 0},
|
||||
)
|
||||
self.assertIn('success', res)
|
||||
self.assertEqual(0, self.get_ff_calls())
|
||||
self.assertNotInFastRetransmit(state)
|
||||
|
||||
def test_enter_fast_retransmit_check_c_not_true(self):
|
||||
"""
|
||||
See self.test_enter_fast_retransmit_check_a_not_true()
|
||||
This test checks if fast retransmit is NOT entered when condition (c)
|
||||
is not true but all others are
|
||||
"""
|
||||
state = self.cong_state()
|
||||
self.assertEqual(0, self.get_ff_calls())
|
||||
self.assertNotInFastRetransmit(state)
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
self._send_msg_and_recv_ack(42, ack_id=15, ack_size=0, ack_clean=True)
|
||||
# make condition (a) true
|
||||
self.cong_report_msg_sent(52)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], 52)
|
||||
# make condition (e) true
|
||||
self.set_same_wnd_adv(True)
|
||||
# condition (b) ack['size'] == 0, (d) ack['id'] == 15
|
||||
# condition (c) not true by setting ack['clean'] = False
|
||||
for _ in range(3):
|
||||
res = self.cong_report_msg_acked(
|
||||
msg={'send_time': 1000, 'size': 42, 'resends': 0},
|
||||
ack={'recv_time': 1100, 'id': 15, 'size': 0,
|
||||
'clean': False, 'wnd': 1234, 'delay': 0},
|
||||
)
|
||||
self.assertIn('success', res)
|
||||
self.assertEqual(0, self.get_ff_calls())
|
||||
self.assertNotInFastRetransmit(state)
|
||||
|
||||
def test_enter_fast_retransmit_check_d_not_true(self):
|
||||
"""
|
||||
See self.test_enter_fast_retransmit_check_a_not_true()
|
||||
This test checks if fast retransmit is NOT entered when condition (d)
|
||||
is not true but all others are
|
||||
"""
|
||||
state = self.cong_state()
|
||||
self.assertEqual(0, self.get_ff_calls())
|
||||
self.assertNotInFastRetransmit(state)
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
self._send_msg_and_recv_ack(42, ack_id=15, ack_size=0, ack_clean=True)
|
||||
# make condition (a) true but also make receiving more ACKs possible
|
||||
self.cong_report_msg_sent(42 * 3)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], 42 * 3)
|
||||
# make condition (e) true
|
||||
self.set_same_wnd_adv(True)
|
||||
# condition (b) ack['size'] == 0, (c) ack['clean'] == True
|
||||
# condition (d) not true by setting ack['id'] = 15 + i
|
||||
for i in range(3):
|
||||
res = self.cong_report_msg_acked(
|
||||
msg={'send_time': 1000, 'size': 42, 'resends': 0},
|
||||
ack={'recv_time': 1100, 'id': 15 + i, 'size': 0,
|
||||
'clean': True, 'wnd': 1234, 'delay': 0},
|
||||
)
|
||||
self.assertIn('success', res)
|
||||
self.assertEqual(0, self.get_ff_calls())
|
||||
self.assertNotInFastRetransmit(state)
|
||||
|
||||
def test_enter_fast_retransmit_check_e_not_true(self):
|
||||
"""
|
||||
See self.test_enter_fast_retransmit_check_a_not_true()
|
||||
This test checks if fast retransmit is NOT entered when condition (e)
|
||||
is not true but all others are
|
||||
"""
|
||||
state = self.cong_state()
|
||||
self.assertEqual(0, self.get_ff_calls())
|
||||
self.assertNotInFastRetransmit(state)
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
self._send_msg_and_recv_ack(42, ack_id=15, ack_size=0, ack_clean=True)
|
||||
# make condition (a) true
|
||||
self.cong_report_msg_sent(52)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], 52)
|
||||
# make condition (e) not true
|
||||
self.set_same_wnd_adv(False)
|
||||
# condition (b) ack['size'] == 0, (c) ack['clean'] == True,
|
||||
# (d) ack['id'] == 15
|
||||
for _ in range(3):
|
||||
res = self.cong_report_msg_acked(
|
||||
msg={'send_time': 1000, 'size': 42, 'resends': 0},
|
||||
ack={'recv_time': 1100, 'id': 15, 'size': 0,
|
||||
'clean': True, 'wnd': 1234, 'delay': 0},
|
||||
)
|
||||
self.assertIn('success', res)
|
||||
self.assertEqual(0, self.get_ff_calls())
|
||||
self.assertNotInFastRetransmit(state)
|
||||
|
||||
def test_enter_fast_retransmit_all_check_true_1(self):
|
||||
"""
|
||||
See self.test_enter_fast_retransmit_check_a_not_true()
|
||||
This test checks if fast retransmit is entered all conditions are true
|
||||
(in the sense that a window is advertised and the current send window
|
||||
is the same)
|
||||
"""
|
||||
state = self.cong_state()
|
||||
self.assertEqual(0, self.get_ff_calls())
|
||||
self.assertNotInFastRetransmit(state)
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
self._send_msg_and_recv_ack(42, ack_id=15, ack_size=0, ack_clean=True)
|
||||
# make condition (a) true
|
||||
self.cong_report_msg_sent(52)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], 52)
|
||||
# make condition (e) true
|
||||
self.set_same_wnd_adv(True)
|
||||
# condition (b) ack['size'] == 0, (c) ack['clean'] == True,
|
||||
# (d) ack['id'] == 15
|
||||
for _ in range(3):
|
||||
res = self.cong_report_msg_acked(
|
||||
msg={'send_time': 1000, 'size': 42, 'resends': 0},
|
||||
ack={'recv_time': 1100, 'id': 15, 'size': 0,
|
||||
'clean': True, 'wnd': 1234, 'delay': 0},
|
||||
)
|
||||
self.assertIn('success', res)
|
||||
self.assertEqual(1, self.get_ff_calls())
|
||||
self.assertInFastRetransmit(self.cong_state())
|
||||
|
||||
def test_enter_fast_retransmit_all_check_true_2(self):
|
||||
"""
|
||||
See self.test_enter_fast_retransmit_check_a_not_true()
|
||||
This test checks if fast retransmit is entered all conditions are true
|
||||
(in the sense that a window is not advertised and thus it is not
|
||||
comparable to the send window)
|
||||
"""
|
||||
state = self.cong_state()
|
||||
self.assertEqual(0, self.get_ff_calls())
|
||||
self.assertNotInFastRetransmit(state)
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
self._send_msg_and_recv_ack(42, ack_id=15, ack_size=0, ack_clean=True)
|
||||
# make condition (a) true
|
||||
self.cong_report_msg_sent(52)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], 52)
|
||||
# return false for same_wnd_adv, just to make sure that `wnd == 0` is
|
||||
# the condition that makes (e) go through
|
||||
self.set_same_wnd_adv(False)
|
||||
# condition (b) ack['size'] == 0, (c) ack['clean'] == True,
|
||||
# (d) ack['id'] == 15, (e) ack['wnd'] == 0
|
||||
for _ in range(3):
|
||||
res = self.cong_report_msg_acked(
|
||||
msg={'send_time': 1000, 'size': 42, 'resends': 0},
|
||||
ack={'recv_time': 1100, 'id': 15, 'size': 0,
|
||||
'clean': True, 'wnd': 0, 'delay': 0},
|
||||
)
|
||||
self.assertIn('success', res)
|
||||
self.assertEqual(1, self.get_ff_calls())
|
||||
self.assertInFastRetransmit(self.cong_state())
|
||||
|
||||
def test_ecn_ce(self):
|
||||
"""
|
||||
https://tools.ietf.org/html/rfc5681#section-3
|
||||
|
||||
> Also, note that the algorithms specified in this document work in
|
||||
> terms of using loss as the signal of congestion. Explicit Congestion
|
||||
> Notification (ECN) could also be used as specified in [RFC3168].
|
||||
|
||||
https://tools.ietf.org/html/rfc3168#section-6.1.2
|
||||
|
||||
> If the sender receives an ECN-Echo (ECE) ACK
|
||||
> packet (that is, an ACK packet with the ECN-Echo flag set in the TCP
|
||||
> header), then the sender knows that congestion was encountered in the
|
||||
> network on the path from the sender to the receiver. The indication
|
||||
> of congestion should be treated just as a congestion loss in non-
|
||||
> ECN-Capable TCP. That is, the TCP source halves the congestion window
|
||||
> "cwnd" and reduces the slow start threshold "ssthresh". The sending
|
||||
> TCP SHOULD NOT increase the congestion window in response to the
|
||||
> receipt of an ECN-Echo ACK packet.
|
||||
|
||||
And https://tools.ietf.org/html/rfc8311#section-4.1
|
||||
|
||||
> The specific change to RFC 3168 is to insert the words "Unless
|
||||
> otherwise specified by an Experimental RFC in the IETF document
|
||||
> stream" at the beginning of the second sentence quoted above.
|
||||
"""
|
||||
state = self.cong_state()
|
||||
init_cwnd = state['cwnd']
|
||||
init_ssthresh = state['ssthresh']
|
||||
self.cong_report_ecn_ce(1204)
|
||||
state = self.cong_state()
|
||||
# the TCP source halves the congestion window
|
||||
self.assertEqual(state['cwnd'], init_cwnd // 2)
|
||||
# and reduces the slow start threshold "ssthresh"
|
||||
self.assertLess(state['ssthresh'], init_ssthresh)
|
||||
|
||||
def test_msg_discarded(self):
|
||||
"""
|
||||
RFC 5681 does not say anything about discarding messages. It's a
|
||||
feature of CongURE. Calling it, should reduce the `in_flight_size`.
|
||||
"""
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
self.cong_report_msg_sent(42)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], 42)
|
||||
self.cong_report_msg_discarded(42)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
|
||||
def test_msgs_lost(self):
|
||||
"""
|
||||
RFC 5681 does not say anything about explicitly lost messages.
|
||||
It's a feature of CongURE. Calling it, should enforce fast retransmit,
|
||||
as implicit loss (3 duplicate ACKs) does the same.
|
||||
"""
|
||||
state = self.cong_state()
|
||||
msgs = [{'size': 24, 'send_time': 1000, 'resends': 0}]
|
||||
flight_size = state['in_flight_size']
|
||||
for msg in msgs:
|
||||
res = self.cong_report_msg_sent(msg_size=msg['size'])
|
||||
self.assertIn('success', res)
|
||||
flight_size += msg['size']
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], flight_size)
|
||||
res = self.cong_report_msgs_lost(msgs)
|
||||
self.assertIn('success', res)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(1, self.get_ff_calls())
|
||||
self.assertInFastRetransmit(state)
|
||||
# explicitly lost messages are not in flight anymore
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
|
||||
|
||||
class TestCongURERenoCustomIncDec(TestCongUREBase):
|
||||
def setUp(self):
|
||||
self.shell.clear()
|
||||
res = self.shell.setup(1)
|
||||
self.assertIn('success', res)
|
||||
res = self.cong_init()
|
||||
self.assertIn('success', res)
|
||||
|
||||
def test_custom_functions_initialized(self):
|
||||
state = self.cong_state()
|
||||
self.assertNotEqual(int(state['consts']['ss_cwnd_inc'], base=16), 0)
|
||||
self.assertNotEqual(int(state['consts']['ca_cwnd_inc'], base=16), 0)
|
||||
self.assertNotEqual(int(state['consts']['fr_cwnd_dec'], base=16), 0)
|
||||
|
||||
def test_slow_start_increase(self):
|
||||
state = self.cong_state()
|
||||
init_cwnd = state['cwnd']
|
||||
self.assertEqual(state['in_flight_size'], 0)
|
||||
self.assertSlowStart(state)
|
||||
# pylint: disable=invalid-name
|
||||
# name chosen to be in line with RFC
|
||||
N = 42
|
||||
self._send_msg_and_recv_ack(N)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['cwnd'], init_cwnd + 1337)
|
||||
self.assertNotInFastRetransmit(state)
|
||||
|
||||
def test_congestion_avoidance_increase(self):
|
||||
state = self.cong_state()
|
||||
init_cwnd = state['cwnd']
|
||||
init_mss = state['mss']
|
||||
self.set_ssthresh(init_cwnd - init_mss)
|
||||
state = self.cong_state()
|
||||
self.assertCongestionAvoidance(state)
|
||||
# pylint: disable=invalid-name
|
||||
# name chosen to be in line with RFC
|
||||
N = 42
|
||||
self._send_msg_and_recv_ack(N)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['cwnd'], init_cwnd + 42)
|
||||
self.assertNotInFastRetransmit(state)
|
||||
|
||||
def test_fast_retransmit_decrease(self):
|
||||
state = self.cong_state()
|
||||
init_cwnd = state['cwnd']
|
||||
msgs = [{'size': 24, 'send_time': 1000, 'resends': 0}]
|
||||
flight_size = state['in_flight_size']
|
||||
for msg in msgs:
|
||||
res = self.cong_report_msg_sent(msg_size=msg['size'])
|
||||
self.assertIn('success', res)
|
||||
flight_size += msg['size']
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['in_flight_size'], flight_size)
|
||||
res = self.cong_report_msgs_lost(msgs)
|
||||
self.assertIn('success', res)
|
||||
state = self.cong_state()
|
||||
self.assertEqual(state['cwnd'], init_cwnd // 8)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
Reference in New Issue
Block a user