1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

Merge pull request #15968 from miri64/congure/feat/congure_abe

congure_abe: initial import of TCP Alternative Backoff with ECN for CongURE
This commit is contained in:
Martine Lenders 2022-10-18 15:50:06 +02:00 committed by GitHub
commit 07c04bc0e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 907 additions and 0 deletions

View File

@ -37,6 +37,10 @@ ifneq (,$(filter congure_%,$(USEMODULE)))
USEMODULE += congure
endif
ifneq (,$(filter congure_abe,$(USEMODULE)))
USEMODULE += congure_reno_methods
endif
ifneq (,$(filter congure_quic,$(USEMODULE)))
USEMODULE += ztimer_msec
endif

View File

@ -8,6 +8,7 @@ if !TEST_KCONFIG
menu "CongURE congestion control abstraction"
depends on USEMODULE_CONGURE
rsource "abe/Kconfig"
rsource "mock/Kconfig"
rsource "quic/Kconfig"
rsource "reno/Kconfig"
@ -23,6 +24,7 @@ menuconfig MODULE_CONGURE
if MODULE_CONGURE
rsource "abe/Kconfig"
rsource "mock/Kconfig"
rsource "quic/Kconfig"
rsource "reno/Kconfig"

View File

@ -1,3 +1,6 @@
ifneq (,$(filter congure_abe,$(USEMODULE)))
DIRS += abe
endif
ifneq (,$(filter congure_quic,$(USEMODULE)))
DIRS += quic
endif

31
sys/congure/abe/Kconfig Normal file
View File

@ -0,0 +1,31 @@
# 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.
if !TEST_KCONFIG
menuconfig KCONFIG_USEMODULE_CONGURE_ABE
bool "Configure TCP-ABE-like congestion control"
depends on USEMODULE_CONGURE_ABE
help
Configure TCP-ABE-like congestion control via Kconfig.
if KCONFIG_USEMODULE_CONGURE_ABE
rsource "Kconfig.config"
endif # KCONFIG_USEMODULE_CONGURE_ABE
endif # !TEST_KCONFIG
if TEST_KCONFIG
menuconfig MODULE_CONGURE_ABE
bool "CongURE implementation of TCP ABE"
depends on MODULE_CONGURE
select MODULE_CONGURE_RENO_METHODS
if MODULE_CONGURE_ABE
rsource "Kconfig.config"
endif # MODULE_CONGURE_ABE
endif # TEST_KCONFIG

View File

@ -0,0 +1,28 @@
# Copyright (c) 2021 Freie Universität
#
# 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.
#
# XXX This file only is required since there is no easy way to use these config
# options with both the final MODULE_CONGURE_ABE (used with TEST_KCONFIG=1) and
# KCONFIG_USEMODULE_CONGURE_ABE (used with TEST_KCONFIG=0) in a # nicely looking
# and easy to migrate way. After migration, the content of this file can be folded
# back into `sys/congure/abe/Kconfig`
config CONGURE_ABE_MULTIPLIER_NUMERATOR_DEFAULT
int "Default numerator of the ABE multiplier"
default 4
help
RFC 8511, section 3.1 defines the ABE multiplier to be 0.8 = 4/5, so
assuming CONGURE_ABE_MULTIPLIER_DENOMINATOR_DEFAULT is 5, this should be
4.
config CONGURE_ABE_MULTIPLIER_DENOMINATOR_DEFAULT
int "Default denominator of the ABE multiplier"
default 5
help
RFC 8511, section 3.1 defines the ABE multiplier to be 0.8 = 4/5, so
assuming CONGURE_ABE_MULTIPLIER_NUMERATOR_DEFAULT is 4, this should be
5.

3
sys/congure/abe/Makefile Normal file
View File

@ -0,0 +1,3 @@
MODULE := congure_abe
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,57 @@
/*
* 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 <stdint.h>
#include "clist.h"
#include "seq.h"
#include "congure/abe.h"
#include "congure/reno.h"
static void _snd_report_ecn_ce(congure_snd_t *cong, ztimer_now_t time);
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 = _snd_report_ecn_ce,
};
static void _snd_report_ecn_ce(congure_snd_t *cong, ztimer_now_t time)
{
congure_abe_snd_t *c = (congure_abe_snd_t *)cong;
const congure_abe_snd_consts_t *consts =
container_of(c->consts, congure_abe_snd_consts_t, reno);
unsigned abe_product = (c->in_flight_size * consts->abe_multiplier_numerator)
/ consts->abe_multiplier_denominator;
(void)time;
c->ssthresh = (abe_product > (c->mss * 2)) ? abe_product : (c->mss * 2);
c->super.cwnd = c->ssthresh;
}
void congure_abe_snd_setup(congure_abe_snd_t *c,
const congure_abe_snd_consts_t *consts)
{
c->super.driver = &_driver;
c->consts = &consts->reno;
}
/** @} */

95
sys/include/congure/abe.h Normal file
View File

@ -0,0 +1,95 @@
/*
* 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_abe CongURE implementation of TCP ABE
* @ingroup sys_congure
* @brief Implementation of the TCP Alternative Backoff with ECN (ABE)
* congestion control mechanism for the CongURE framework.
*
* @see [RFC 8511](https://tools.ietf.org/html/rfc8511)
* @{
*
* @file
* @brief
*
* @author Martine S. Lenders <m.lenders@fu-berlin.de>
*/
#ifndef CONGURE_ABE_H
#define CONGURE_ABE_H
#include <stdint.h>
#include "congure/config.h"
#include "congure/reno.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Alias of CongURE Reno state object for CongURE ABE.
*
* @extends congure_reno_snd_t
*/
typedef congure_reno_snd_t congure_abe_snd_t;
/**
* @brief Constants for the congestion control.
*
* @extends congure_reno_snd_consts_t
*/
typedef struct {
congure_reno_snd_consts_t reno; /**< TCP Reno constants */
/**
* @brief numerator for the `beta_{ecn} `factor used to set the slow start
* threshold on an ECN CE.
*
* Use @ref CONFIG_CONGURE_ABE_MULTIPLIER_NUMERATOR_DEFAULT for a default
* value
*/
uint8_t abe_multiplier_numerator;
/**
* @brief numerator for the `beta_{ecn} `factor used to set the slow start
* threshold on an ECN CE.
*
* Use @ref CONFIG_CONGURE_ABE_MULTIPLIER_DENOMINATOR_DEFAULT for a default
* value
*/
uint8_t abe_multiplier_denominator;
} congure_abe_snd_consts_t;
/**
* @brief Set's up the driver for a CongURE ABE object
*
* @param[in] c A CongURE ABE object.
* @param[in] consts The constants to use for @p c
*/
void congure_abe_snd_setup(congure_abe_snd_t *c,
const congure_abe_snd_consts_t *consts);
/**
* @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
*/
static inline void congure_abe_set_mss(congure_abe_snd_t *c, unsigned mss)
{
congure_reno_set_mss((congure_reno_snd_t *)c, mss);
}
#ifdef __cplusplus
}
#endif
#endif /* CONGURE_ABE_H */
/** @} */

View File

@ -24,6 +24,26 @@
extern "C" {
#endif
/**
* @brief Default numerator of the ABE multiplier (0.8)
*
* @note Use with @ref sys_congure_abe
* @see [RFC 8511, section 3.1](https://tools.ietf.org/html/rfc8511#section-3.1)
*/
#ifndef CONFIG_CONGURE_ABE_MULTIPLIER_NUMERATOR_DEFAULT
#define CONFIG_CONGURE_ABE_MULTIPLIER_NUMERATOR_DEFAULT (4U)
#endif
/**
* @brief Default denominator of the ABE multiplier (0.8)
*
* @note Use with @ref sys_congure_abe
* @see [RFC 8511, section 3.1](https://tools.ietf.org/html/rfc8511#section-3.1)
*/
#ifndef CONFIG_CONGURE_ABE_MULTIPLIER_DENOMINATOR_DEFAULT
#define CONFIG_CONGURE_ABE_MULTIPLIER_DENOMINATOR_DEFAULT (5U)
#endif
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,18 @@
include ../Makefile.tests_common
USEMODULE += congure_abe
USEMODULE += congure_test
USEMODULE += fmt
USEMODULE += shell
INCLUDES += -I$(CURDIR)
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

View File

@ -0,0 +1,8 @@
BOARD_INSUFFICIENT_MEMORY := \
arduino-duemilanove \
arduino-leonardo \
arduino-nano \
arduino-uno \
atmega328p \
atmega328p-xplained-mini \
#

View File

@ -0,0 +1,30 @@
Tests for the CongURE TCP ABE implementation
=============================================
This test tests the `congure_abe` 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.

View 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

View File

@ -0,0 +1,9 @@
CONFIG_MODULE_CONGURE=y
CONFIG_MODULE_CONGURE_ABE=y
CONFIG_MODULE_CONGURE_TEST=y
CONFIG_MODULE_FMT=y
CONFIG_MODULE_SEQ=y
CONFIG_MODULE_SHELL=y
CONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE=6
CONFIG_SHELL_NO_ECHO=y

View File

@ -0,0 +1,76 @@
/*
* 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_abe_snd_t *c);
static bool _same_wnd_adv(congure_abe_snd_t *c, congure_snd_ack_t *ack);
static const congure_abe_snd_consts_t _consts[] = {
{
.reno = {
.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,
},
.abe_multiplier_numerator = CONFIG_CONGURE_ABE_MULTIPLIER_NUMERATOR_DEFAULT,
.abe_multiplier_denominator = CONFIG_CONGURE_ABE_MULTIPLIER_DENOMINATOR_DEFAULT,
}
};
int congure_test_snd_setup(congure_test_snd_t *c, unsigned id)
{
if (id >= ARRAY_SIZE(_consts)) {
return -1;
}
_fr_calls = 0;
congure_abe_snd_setup(c, &_consts[id]);
return 0;
}
unsigned congure_abe_test_get_fr_calls(void)
{
return _fr_calls;
}
void congure_abe_test_set_same_wnd_adv_res(bool value)
{
_same_wnd_adv_res = value;
}
static void _fr(congure_abe_snd_t *c)
{
(void)c;
_fr_calls++;
}
static bool _same_wnd_adv(congure_abe_snd_t *c, congure_snd_ack_t *ack)
{
(void)c;
(void)ack;
return _same_wnd_adv_res;
}
/** @} */

View 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/abe.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef congure_abe_snd_t congure_test_snd_t;
int congure_test_snd_setup(congure_test_snd_t *c, unsigned id);
unsigned congure_abe_test_get_fr_calls(void);
void congure_abe_test_set_same_wnd_adv_res(bool value);
#ifdef __cplusplus
}
#endif
#endif /* CONGURE_IMPL_H */
/** @} */

151
tests/congure_abe/main.c Normal file
View File

@ -0,0 +1,151 @@
/*
* 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"
static int _json_statham(int argc, char **argv);
static int _set_cwnd(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_abe_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 },
{ "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)
{
char line_buf[SHELL_DEFAULT_BUFSIZE];
shell_run(shell_commands, line_buf, SHELL_DEFAULT_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_abe_consts(const congure_abe_snd_consts_t *consts)
{
print_str("\"consts\":");
if (consts) {
print_str("{");
PRINT_FIELD_PTR(&consts->reno, fr);
PRINT_FIELD_PTR(&consts->reno, same_wnd_adv);
PRINT_FIELD_PTR(&consts->reno, ss_cwnd_inc);
PRINT_FIELD_PTR(&consts->reno, ca_cwnd_inc);
PRINT_FIELD_PTR(&consts->reno, fr_cwnd_dec);
PRINT_FIELD_UINT(consts->reno, init_mss);
PRINT_FIELD_UINT(consts->reno, cwnd_upper);
PRINT_FIELD_UINT(consts->reno, cwnd_lower);
PRINT_FIELD_UINT(consts->reno, init_ssthresh);
PRINT_FIELD_UINT(consts->reno, frthresh);
PRINT_FIELD_UINT(*consts, abe_multiplier_numerator);
PRINT_FIELD_UINT(*consts, abe_multiplier_denominator);
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_abe_consts(
(congure_abe_snd_consts_t *)_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_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\":\"`ssthresh` not 16 bit wide\"}\n");
}
_congure_state.super.cwnd = (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_abe_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_abe_test_set_same_wnd_adv_res(
(bool)scn_u32_dec(argv[1], strlen(argv[1]))
);
return 0;
}
/** @} */

332
tests/congure_abe/tests/01-run.py Executable file
View File

@ -0,0 +1,332 @@
#! /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.start_term()
if cls.DEBUG:
cls.ctrl.term.logfile = sys.stdout
cls.ctrl.reset()
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 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(f'set_same_wnd_adv {value:d}')
def set_cwnd(self, cwnd):
self.exec_cmd(f'set_cwnd {cwnd}')
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_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)
class TestCongUREABEWithoutSetup(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 TestCongUREABEDefaultInitTests(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)
# https://tools.ietf.org/html/rfc8511#section-3.1
beta_ecn = (state['consts']['abe_multiplier_numerator'] /
state['consts']['abe_multiplier_denominator'])
self.assertAlmostEqual(beta_ecn, 0.8)
self.assertEqual(state['consts']['frthresh'], 3)
def test_init(self):
"""
This is inherited from `congure_reno`, so it should be the same as
in `tests/congure_reno`.
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 TestCongUREABE(TestCongUREBase):
"""
Most functionality should be the same as for `congure_reno`, except
for the behavior of `report_ecn_ce`. So only test some basics from
`tests/congure_reno` and focus testing on `report_ecn_ce`
"""
def setUp(self):
super().setUp()
res = self.shell.setup(0)
self.assertIn('success', res)
res = self.cong_init()
self.assertIn('success', 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)
def test_slow_start_increase(self):
# pylint: disable=invalid-name
# name chosen to be in line with RFC
"""
See test_slow_start_increase_large_N() from `tests/congure_reno`
"""
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_enter_fast_retransmit(self):
"""
See self.test_enter_fast_retransmit_all_check_true() from
`tests/congure_reno`
"""
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(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_ecn_ce_small_flight_size(self):
"""
https://tools.ietf.org/html/rfc8511#section-3
> As permitted by RFC 8311, this document specifies a sender-side
> change to TCP where receipt of a packet with the ECN-Echo flag SHOULD
> trigger the TCP source to set the slow start threshold (ssthresh) to
> 0.8 times the FlightSize, with a lower bound of 2 * SMSS applied to
> the result (where SMSS stands for Sender Maximum Segment Size)). As
> in [RFC5681], the TCP sender also reduces the cwnd value to no more
> than the new ssthresh value. Section 6.1.2 of RFC 3168 provides
> guidance on setting a cwnd less than 2 * SMSS.
"""
state = self.cong_state()
beta_ecn = (state['consts']['abe_multiplier_numerator'] /
state['consts']['abe_multiplier_denominator'])
self.assertAlmostEqual(beta_ecn, 0.8)
flight_size = 150
# put some bytes in the air
self.cong_report_msg_sent(flight_size)
self.cong_report_ecn_ce(1204)
state = self.cong_state()
# [...] set the slow start threshold (ssthresh) to 0.8 times the
# FlightSize, with a lower bound of 2 * SMSS applied to the result
self.assertEqual(state['ssthresh'], 2 * state['mss'])
# the TCP sender also reduces the cwnd value to no more
# than the new ssthresh value
self.assertLessEqual(state['cwnd'], state['ssthresh'])
def test_ecn_ce_large_flight_size(self):
"""
Same as test_ecn_ce_small_flight_size, but with flight size
larger than (2 * SMSS / 0.8)
"""
state = self.cong_state()
beta_ecn = (state['consts']['abe_multiplier_numerator'] /
state['consts']['abe_multiplier_denominator'])
self.assertAlmostEqual(beta_ecn, 0.8)
flight_size = 3 * state['mss']
# increase congestion window large enough to send all those bytes
self.set_cwnd(flight_size)
# put some bytes in the air
for _ in range(3):
self.cong_report_msg_sent(state['mss'])
self.cong_report_ecn_ce(1204)
state = self.cong_state()
# [...] set the slow start threshold (ssthresh) to 0.8 times the
# FlightSize, with a lower bound of 2 * SMSS applied to the result
self.assertEqual(state['ssthresh'], beta_ecn * flight_size)
# the TCP sender also reduces the cwnd value to no more
# than the new ssthresh value
self.assertLessEqual(state['cwnd'], state['ssthresh'])
if __name__ == '__main__':
unittest.main()