1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/tests/congure_abe/tests/01-run.py
2022-10-18 09:23:06 +02:00

333 lines
12 KiB
Python
Executable File

#! /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()