From 914ca26bc748550c4563d265dbdd314b00f621f6 Mon Sep 17 00:00:00 2001 From: Martine Lenders Date: Fri, 5 Mar 2021 18:04:11 +0100 Subject: [PATCH 1/2] gnrc_sixlowpan_frag_sfr_congure_sfr: initial import --- makefiles/pseudomodules.inc.mk | 6 + .../net/gnrc/sixlowpan/frag/sfr/congure.h | 2 +- sys/net/gnrc/Makefile.dep | 4 + .../sixlowpan/frag/sfr/congure_sfr.c | 115 ++++++++++++++++++ 4 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 sys/net/gnrc/network_layer/sixlowpan/frag/sfr/congure_sfr.c diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 25169290798ef..6cbb3475329da 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -236,6 +236,12 @@ PSEUDOMODULES += gnrc_sixlowpan_frag_sfr_stats ## @{ ## PSEUDOMODULES += gnrc_sixlowpan_frag_sfr_congure +## @defgroup net_gnrc_sixlowpan_frag_sfr_congure_sfr gnrc_sixlowpan_frag_sfr_congure_sfr: Appendix C +## @brief Basic congestion control for 6LoWPAN SFR as proposed in Appendix C of RFC 8931 +## @see [RFC 8931, Appendix C](https://tools.ietf.org/html/rfc8931#section-appendix.c) +## @{ +PSEUDOMODULES += gnrc_sixlowpan_frag_sfr_congure_sfr +## @} ## @} PSEUDOMODULES += gnrc_sixlowpan_iphc_nhc PSEUDOMODULES += gnrc_sixlowpan_nd_border_router diff --git a/sys/include/net/gnrc/sixlowpan/frag/sfr/congure.h b/sys/include/net/gnrc/sixlowpan/frag/sfr/congure.h index 068eaee12f00c..6345e3e3367d3 100644 --- a/sys/include/net/gnrc/sixlowpan/frag/sfr/congure.h +++ b/sys/include/net/gnrc/sixlowpan/frag/sfr/congure.h @@ -15,7 +15,7 @@ * When included, this module enables congestion control for 6LoWPAN Selective Fragment Recovery * (SFR). The flavor of congestion control can be selected using the following sub-modules: * - * - TBD + * - @ref net_gnrc_sixlowpan_frag_sfr_congure_sfr (the default) * @{ * * @file diff --git a/sys/net/gnrc/Makefile.dep b/sys/net/gnrc/Makefile.dep index f61bb305cc987..fdec26addd542 100644 --- a/sys/net/gnrc/Makefile.dep +++ b/sys/net/gnrc/Makefile.dep @@ -246,6 +246,10 @@ endif ifneq (,$(filter gnrc_sixlowpan_frag_sfr_congure,$(USEMODULE))) USEMODULE += gnrc_sixlowpan_frag_sfr + ifeq (,$(filter gnrc_sixlowpan_frag_sfr_congure_% congure_mock,$(USEMODULE))) + # pick Appendix C as default congestion control + USEMODULE += gnrc_sixlowpan_frag_sfr_congure_sfr + endif endif ifneq (,$(filter gnrc_sixlowpan_frag_sfr_ecn_%,$(USEMODULE))) diff --git a/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/congure_sfr.c b/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/congure_sfr.c new file mode 100644 index 0000000000000..b6e446b552ac9 --- /dev/null +++ b/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/congure_sfr.c @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2021 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @{ + * + * @file + * @author Martine Lenders + */ + +#include "kernel_defines.h" +#include "congure.h" +#include "net/gnrc/sixlowpan/config.h" + +#include "net/gnrc/sixlowpan/frag/sfr/congure.h" + +/* This implements a very simple SFR as suggested in + * https://datatracker.ietf.org/doc/html/rfc8931#appendix-C */ + +typedef congure_snd_t congure_sfr_snd_t; + +static void _snd_init(congure_snd_t *cong, void *ctx); +static int32_t _snd_inter_msg_interval(congure_snd_t *cong, unsigned msg_size); +static void _snd_report_msg_sent(congure_snd_t *cong, unsigned sent_size); +static void _snd_report_msg_discarded(congure_snd_t *cong, unsigned msg_size); +static void _snd_report_msgs_lost(congure_snd_t *cong, congure_snd_msg_t *msgs); +static void _snd_report_msgs_timeout(congure_snd_t *cong, congure_snd_msg_t *msgs); +static void _snd_report_msg_acked(congure_snd_t *cong, congure_snd_msg_t *msg, + congure_snd_ack_t *ack); +static void _snd_report_ecn_ce(congure_snd_t *cong, ztimer_now_t time); + +static congure_sfr_snd_t _sfr_congures_sfr[CONFIG_GNRC_SIXLOWPAN_FRAG_FB_SIZE]; +static const congure_snd_driver_t _driver = { + .init = _snd_init, + .inter_msg_interval = _snd_inter_msg_interval, + .report_msg_sent = _snd_report_msg_sent, + .report_msg_discarded = _snd_report_msg_discarded, + .report_msgs_timeout = _snd_report_msgs_timeout, + .report_msgs_lost = _snd_report_msgs_lost, + .report_msg_acked = _snd_report_msg_acked, + .report_ecn_ce = _snd_report_ecn_ce, +}; + +congure_snd_t *gnrc_sixlowpan_frag_sfr_congure_snd_get(void) +{ + for (unsigned i = 0; i < ARRAY_SIZE(_sfr_congures_sfr); i++) { + if (_sfr_congures_sfr[i].driver == NULL) { + _sfr_congures_sfr[i].driver = &_driver; + return &_sfr_congures_sfr[i]; + } + } + return NULL; +} + +static void _snd_init(congure_snd_t *cong, void *ctx) +{ + (void)ctx; + cong->cwnd = CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE; +} + +static int32_t _snd_inter_msg_interval(congure_snd_t *cong, unsigned msg_size) +{ + (void)cong; + (void)msg_size; + return CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US; +} + +static void _snd_report_msg_sent(congure_snd_t *cong, unsigned sent_size) +{ + (void)cong; + (void)sent_size; +} + +static void _snd_report_msg_discarded(congure_snd_t *cong, unsigned msg_size) +{ + (void)cong; + (void)msg_size; +} + +static void _snd_report_msgs_lost(congure_snd_t *cong, congure_snd_msg_t *msgs) +{ + /* Appendix C defines loss as timeout, so this does nothing */ + (void)cong; + (void)msgs; +} + +static void _snd_report_msgs_timeout(congure_snd_t *cong, + congure_snd_msg_t *msgs) +{ + (void)msgs; + if (cong->cwnd > 1U) { + cong->cwnd /= 2U; + } +} + +static void _snd_report_msg_acked(congure_snd_t *cong, congure_snd_msg_t *msg, + congure_snd_ack_t *ack) +{ + (void)cong; + (void)msg; + (void)ack; +} + +static void _snd_report_ecn_ce(congure_snd_t *cong, ztimer_now_t time) +{ + (void)time; + if (cong->cwnd > 1U) { + cong->cwnd--; + } +} From ecdc64eb2f44d9a19bab7b521f99857f8132fec7 Mon Sep 17 00:00:00 2001 From: Martine Lenders Date: Fri, 5 Mar 2021 18:04:45 +0100 Subject: [PATCH 2/2] tests: add test application for CongURE implementations with SFR --- .../Makefile | 49 ++++++ .../Makefile.ci | 56 +++++++ .../README.md | 8 + .../main.c | 39 +++++ .../tests/01-run.py | 158 ++++++++++++++++++ 5 files changed, 310 insertions(+) create mode 100644 tests/gnrc_sixlowpan_frag_sfr_congure_impl/Makefile create mode 100644 tests/gnrc_sixlowpan_frag_sfr_congure_impl/Makefile.ci create mode 100644 tests/gnrc_sixlowpan_frag_sfr_congure_impl/README.md create mode 100644 tests/gnrc_sixlowpan_frag_sfr_congure_impl/main.c create mode 100755 tests/gnrc_sixlowpan_frag_sfr_congure_impl/tests/01-run.py diff --git a/tests/gnrc_sixlowpan_frag_sfr_congure_impl/Makefile b/tests/gnrc_sixlowpan_frag_sfr_congure_impl/Makefile new file mode 100644 index 0000000000000..4e97854f3a1bb --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr_congure_impl/Makefile @@ -0,0 +1,49 @@ +include ../Makefile.tests_common + +USEMODULE += auto_init_gnrc_netif +USEMODULE += gnrc_ipv6_router_default +USEMODULE += gnrc_sixlowpan_frag_sfr +USEMODULE += gnrc_udp +USEMODULE += gnrc_rpl +USEMODULE += auto_init_gnrc_rpl +USEMODULE += gnrc_pktdump +USEMODULE += gnrc_icmpv6_echo +USEMODULE += shell +USEMODULE += shell_cmds_default +USEMODULE += shell_cmd_gnrc_pktbuf +USEMODULE += shell_cmd_gnrc_udp +USEMODULE += ps +USEMODULE += netstats_l2 +USEMODULE += netstats_ipv6 +USEMODULE += netstats_rpl + +ifeq (native, $(BOARD)) + USEMODULE += socket_zep + USEMODULE += socket_zep_hello + USEMODULE += netdev + TERMFLAGS = -z 127.0.0.1:17754 # Murdock has no IPv6 support +else + USEMODULE += netdev_default + # automated test only works on native + TESTS= +endif + +CONGURE_IMPL ?= congure_sfr + +ifeq (congure_sfr,$(CONGURE_IMPL)) + USEMODULE += gnrc_sixlowpan_frag_sfr_congure_sfr +else + $(error "Unknown CongURE implementation `$(CONGURE_IMPL)`") +endif + +.PHONY: zep_dispatch + +zep_dispatch: + $(Q)env -u CC -u CFLAGS $(MAKE) -C $(RIOTTOOLS) $@ + +TERMDEPS += zep_dispatch + +include $(RIOTBASE)/Makefile.include + +# Set a custom channel if needed +include $(RIOTMAKE)/default-radio-settings.inc.mk diff --git a/tests/gnrc_sixlowpan_frag_sfr_congure_impl/Makefile.ci b/tests/gnrc_sixlowpan_frag_sfr_congure_impl/Makefile.ci new file mode 100644 index 0000000000000..95fdac193f1e9 --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr_congure_impl/Makefile.ci @@ -0,0 +1,56 @@ +BOARD_INSUFFICIENT_MEMORY := \ + airfy-beacon \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + atxmega-a3bu-xplained \ + blackpill-stm32f103c8 \ + blackpill-stm32f103cb \ + bluepill-stm32f030c8 \ + bluepill-stm32f103c8 \ + bluepill-stm32f103cb \ + calliope-mini \ + derfmega128 \ + hifive1 \ + hifive1b \ + i-nucleo-lrwan1 \ + im880b \ + lsn50 \ + microbit \ + microduino-corerf \ + msb-430 \ + msb-430h \ + nrf51dongle \ + nrf6310 \ + nucleo-f030r8 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-f070rb \ + nucleo-f072rb \ + nucleo-f302r8 \ + nucleo-f303k8 \ + nucleo-f334r8 \ + nucleo-l011k4 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + samd10-xmini \ + saml10-xpro \ + saml11-xpro \ + slstk3400a \ + stk3200 \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32f7508-dk \ + stm32g0316-disco \ + stm32l0538-disco \ + stm32mp157c-dk2 \ + telosb \ + waspmote-pro \ + yunjia-nrf51822 \ + z1 \ + zigduino \ + # diff --git a/tests/gnrc_sixlowpan_frag_sfr_congure_impl/README.md b/tests/gnrc_sixlowpan_frag_sfr_congure_impl/README.md new file mode 100644 index 0000000000000..d7c4cabed319d --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr_congure_impl/README.md @@ -0,0 +1,8 @@ +SFR CongURE implementation test +=============================== +This test is largely based on the [`gnrc_networking`][1] example but is changed +so SFR with different CongURE implementations can be tested. When `CONGURE_IMPL` +is not set in the environment, `gnrc_sixlowpan_frag_sfr_congure_sfr` is used, +other implementations can be used with `congure_`. + +[1]: https://github.com/RIOT-OS/RIOT/tree/master/examples/gnrc_networking diff --git a/tests/gnrc_sixlowpan_frag_sfr_congure_impl/main.c b/tests/gnrc_sixlowpan_frag_sfr_congure_impl/main.c new file mode 100644 index 0000000000000..843d7968a9738 --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr_congure_impl/main.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015-21 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. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * + * @author Hauke Petersen + * @author Martine Lenders + * + * @} + */ + +#include + +#include "fmt.h" +#include "shell.h" +#include "msg.h" + +#define MAIN_QUEUE_SIZE (8) +static msg_t _main_msg_queue[MAIN_QUEUE_SIZE]; + +int main(void) +{ + msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE); + + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE); + + /* should be never reached */ + return 0; +} diff --git a/tests/gnrc_sixlowpan_frag_sfr_congure_impl/tests/01-run.py b/tests/gnrc_sixlowpan_frag_sfr_congure_impl/tests/01-run.py new file mode 100755 index 0000000000000..8265a9191b55a --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr_congure_impl/tests/01-run.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2021 Benjamin Valentin +# Copyright (C) 2023 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 contextlib +import subprocess +import sys +import time + +from subprocess import Popen +from riotctrl_shell.gnrc import GNRCICMPv6Echo, GNRCICMPv6EchoParser, GNRCIPv6NIB +from riotctrl_shell.netif import Ifconfig +from riotctrl.ctrl import RIOTCtrlBoardFactory +from riotctrl_ctrl import native +from riotctrl_shell.netif import IfconfigListParser + +PARSERS = { + "ping6": GNRCICMPv6EchoParser(), + "ifconfig": IfconfigListParser(), +} + + +class RIOTCtrlAppFactory(RIOTCtrlBoardFactory): + + def __init__(self): + super().__init__(board_cls={ + 'native': native.NativeRIOTCtrl, + }) + self.ctrl_list = list() + + def __enter__(self): + return self + + def __exit__(self, *exc): + for ctrl in self.ctrl_list: + ctrl.stop_term() + + def get_shell(self, application_directory='.', env={'BOARD': 'native'}): + # retrieve a RIOTCtrl Object + ctrl = super().get_ctrl( + env=env, + application_directory=application_directory + ) + # append ctrl to list + self.ctrl_list.append(ctrl) + # start terminal + ctrl.start_term() + ctrl.term.logfile = sys.stdout + # return ctrl with started terminal + return Shell(ctrl) + + def get_shells(self, num=1): + terms = [] + for i in range(num): + terms.append(self.get_shell()) + return terms + + +class Shell(Ifconfig, GNRCICMPv6Echo, GNRCIPv6NIB): + pass + + +def first_netif_and_addr_by_scope(ifconfig_out, scope): + netifs = PARSERS["ifconfig"].parse(ifconfig_out) + key = next(iter(netifs)) + netif = netifs[key] + return ( + key, + [addr["addr"] for addr in netif["ipv6_addrs"] if addr["scope"] == scope][0], + ) + + +def global_addr(ifconfig_out): + return first_netif_and_addr_by_scope(ifconfig_out, "global") + + +@contextlib.contextmanager +def rpl_nodes(factory, zep_dispatch): + # linear topology with 4 nodes + topology = ("A B\n" + "B C\n" + "C D\n") + zep_dispatch.stdin.write(topology.encode()) + zep_dispatch.stdin.close() + + # create native instances + nodes = factory.get_shells(4) + A, _, _, D = nodes + + netifs = PARSERS["ifconfig"].parse(A.ifconfig_list()) + iface = next(iter(netifs)) + # add prefix to root node + A.nib_prefix_add(iface, "2001:db8::/64") + A.ifconfig_add(iface, "2001:db8::1/64") + A.cmd("rpl root 0 2001:db8::1") + root_addr = global_addr(A.ifconfig_list())[1] + + # wait for the creation of the DODAG + time.sleep(10) + yield root_addr, D + for node in nodes: + node.stop_term() + + +def assert_result(result, exp_packet_loss, exp_responses, exp_ttl): + assert result['stats']['packet_loss'] < exp_packet_loss + assert result['stats']['rx'] >= exp_responses + assert result['replies'][0]['ttl'] == exp_ttl + + +def test_reachability(factory, zep_dispatch): + with rpl_nodes(factory, zep_dispatch) as (root_addr, D): + # ping root node from last node + parser = PARSERS["ping6"] + result = parser.parse(D.ping6(root_addr)) + # assert packetloss is under 34%" + # assert at least one response + # 2 intermediate hops, 64 - 2 + assert_result(result, 34, 1, 64 - 2) + + +def test_fragmentation(factory, zep_dispatch): + with rpl_nodes(factory, zep_dispatch) as (root_addr, D): + # ping root node from last node + parser = PARSERS["ping6"] + result = parser.parse(D.ping6(root_addr, count=100, interval=30, packet_size=500)) + # assert packetloss is under 90%" + # assert at least one response + # 2 intermediate hops, 64 - 2 + assert_result(result, 90, 1, 64 - 2) + + +@contextlib.contextmanager +def run_zep_dispatch(): + zep_dispatch = Popen( + [ + '../../dist/tools/zep_dispatch/bin/zep_dispatch', + '-t', + '-', + '127.0.0.1', + '17754' + ], + stdin=subprocess.PIPE + ) + try: + yield zep_dispatch + finally: + zep_dispatch.terminate() + + +if __name__ == "__main__": + with RIOTCtrlAppFactory() as factory, run_zep_dispatch() as zep_dispatch: + test_fragmentation(factory, zep_dispatch)