diff --git a/sys/include/net/gnrc/netif.h b/sys/include/net/gnrc/netif.h index e2a3e510c2..6bfc214fdd 100644 --- a/sys/include/net/gnrc/netif.h +++ b/sys/include/net/gnrc/netif.h @@ -422,7 +422,7 @@ static inline int gnrc_netif_ipv6_addrs_get(const gnrc_netif_t *netif, * @return -ENOTSUP, if @p netif doesn't support IPv6. */ static inline int gnrc_netif_ipv6_addr_add(const gnrc_netif_t *netif, - ipv6_addr_t *addr, unsigned pfx_len, + const ipv6_addr_t *addr, unsigned pfx_len, uint8_t flags) { assert(netif != NULL); @@ -447,7 +447,7 @@ static inline int gnrc_netif_ipv6_addr_add(const gnrc_netif_t *netif, * @return -ENOTSUP, if @p netif doesn't support IPv6. */ static inline int gnrc_netif_ipv6_addr_remove(const gnrc_netif_t *netif, - ipv6_addr_t *addr) + const ipv6_addr_t *addr) { assert(netif != NULL); assert(addr != NULL); @@ -656,6 +656,23 @@ static inline msg_bus_t* gnrc_netif_get_bus(gnrc_netif_t *netif, assert(type < GNRC_NETIF_BUS_NUMOF); return &netif->bus[type]; } + +/** + * @brief Wait for a global address to become available. + * This function blocks until a valid global address has been + * configured, e.g. by receiving a router advertisement or via DHCPv6. + * + * Requires the `gnrc_netif_bus` module. + * + * @param netif pointer to the interface + * May be NULL, then this checks for a global address + * on *any* interface. + * @param timeout_ms Time to wait for an address to become available, in ms. + * + * @return true if a global address is configured + */ +bool gnrc_netif_ipv6_wait_for_global_address(gnrc_netif_t *netif, + uint32_t timeout_ms); #endif /* MODULE_GNRC_NETIF_BUS */ #ifdef __cplusplus diff --git a/sys/net/gnrc/netif/gnrc_netif.c b/sys/net/gnrc/netif/gnrc_netif.c index a4d7f70fe8..f0ba58b1ca 100644 --- a/sys/net/gnrc/netif/gnrc_netif.c +++ b/sys/net/gnrc/netif/gnrc_netif.c @@ -41,7 +41,7 @@ #include "fmt.h" #include "log.h" #include "sched.h" -#if (CONFIG_GNRC_NETIF_MIN_WAIT_AFTER_SEND_US > 0U) +#if IS_USED(MODULE_XTIMER) || IS_USED(MODULE_ZTIMER_XTIMER_COMPAT) #include "xtimer.h" #endif @@ -1298,6 +1298,105 @@ int gnrc_netif_ipv6_add_prefix(gnrc_netif_t *netif, out: return res; } + +#if IS_USED(MODULE_GNRC_NETIF_BUS) +static bool _has_global_addr(gnrc_netif_t *netif) +{ + bool has_global = false; + + gnrc_netif_acquire(netif); + + for (unsigned i = 0; i < CONFIG_GNRC_NETIF_IPV6_ADDRS_NUMOF; i++) { + if (ipv6_addr_is_unspecified(&netif->ipv6.addrs[i])) { + continue; + } + if (!ipv6_addr_is_link_local(&netif->ipv6.addrs[i])) { + has_global = true; + break; + } + } + + gnrc_netif_release(netif); + return has_global; +} + +static void _netif_bus_attach_and_subscribe_addr_valid(gnrc_netif_t *netif, + msg_bus_entry_t *sub) +{ + msg_bus_t *bus = gnrc_netif_get_bus(netif, GNRC_NETIF_BUS_IPV6); + msg_bus_attach(bus, sub); + msg_bus_subscribe(sub, GNRC_IPV6_EVENT_ADDR_VALID); +} + +static void _netif_bus_detach(gnrc_netif_t *netif, msg_bus_entry_t *sub) +{ + msg_bus_t *bus = gnrc_netif_get_bus(netif, GNRC_NETIF_BUS_IPV6); + msg_bus_detach(bus, sub); +} + +bool gnrc_netif_ipv6_wait_for_global_address(gnrc_netif_t *netif, + uint32_t timeout_ms) +{ + unsigned netif_numof = gnrc_netif_numof(); + + /* no interfaces */ + if (netif_numof == 0) { + return false; + } + + msg_bus_entry_t subs[netif_numof]; + bool has_global = false; + + if (netif) { + if (_has_global_addr(netif)) { + return true; + } + + _netif_bus_attach_and_subscribe_addr_valid(netif, &subs[0]); + } else { + /* subscribe to all interfaces */ + for (unsigned count = 0; + (netif = gnrc_netif_iter(netif)); + count++) { + if (_has_global_addr(netif)) { + has_global = true; + } + + _netif_bus_attach_and_subscribe_addr_valid(netif, &subs[count]); + } + } + + /* wait for global address */ + msg_t m; + while (!has_global) { + if (xtimer_msg_receive_timeout(&m, timeout_ms * US_PER_MS) < 0) { + DEBUG_PUTS("gnrc_netif: timeout waiting for prefix"); + break; + } + + if (ipv6_addr_is_link_local(m.content.ptr)) { + DEBUG_PUTS("gnrc_netif: got link-local address"); + } else { + DEBUG_PUTS("gnrc_netif: got global address"); + has_global = true; + } + } + + /* called with a given interface */ + if (netif != NULL) { + _netif_bus_detach(netif, &subs[0]); + } else { + /* unsubscribe all */ + for (unsigned count = 0; + (netif = gnrc_netif_iter(netif)); + count++) { + _netif_bus_detach(netif, &subs[count]); + } + } + + return has_global; +} +#endif /* IS_USED(MODULE_GNRC_NETIF_BUS) */ #endif /* IS_USED(MODULE_GNRC_NETIF_IPV6) */ static void _update_l2addr_from_dev(gnrc_netif_t *netif) diff --git a/tests/gnrc_netif_ipv6_wait_for_global_address/Makefile b/tests/gnrc_netif_ipv6_wait_for_global_address/Makefile new file mode 100644 index 0000000000..b745e1bf2a --- /dev/null +++ b/tests/gnrc_netif_ipv6_wait_for_global_address/Makefile @@ -0,0 +1,19 @@ +include ../Makefile.tests_common + +USEMODULE += embunit +USEMODULE += gnrc_netif +USEMODULE += gnrc_netif_bus +USEMODULE += gnrc_ipv6 +USEMODULE += netdev_test +USEMODULE += xtimer + +# deactivate automatically emitted packets from IPv6 neighbor discovery +CFLAGS += -DCONFIG_GNRC_IPV6_NIB_ARSM=0 +CFLAGS += -DCONFIG_GNRC_IPV6_NIB_SLAAC=0 +CFLAGS += -DCONFIG_GNRC_IPV6_NIB_NO_RTR_SOL=1 +CFLAGS += -DGNRC_NETIF_ADDRS_NUMOF=16 +CFLAGS += -DGNRC_NETIF_GROUPS_NUMOF=8 +CFLAGS += -DLOG_LEVEL=LOG_NONE +CFLAGS += -DTEST_SUITES + +include $(RIOTBASE)/Makefile.include diff --git a/tests/gnrc_netif_ipv6_wait_for_global_address/Makefile.ci b/tests/gnrc_netif_ipv6_wait_for_global_address/Makefile.ci new file mode 100644 index 0000000000..28ea82d339 --- /dev/null +++ b/tests/gnrc_netif_ipv6_wait_for_global_address/Makefile.ci @@ -0,0 +1,28 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + bluepill-stm32f030c8 \ + i-nucleo-lrwan1 \ + msb-430 \ + msb-430h \ + nucleo-f030r8 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-f303k8 \ + nucleo-f334r8 \ + nucleo-l011k4 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + samd10-xmini \ + slstk3400a \ + stk3200 \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32l0538-disco \ + waspmote-pro \ + # diff --git a/tests/gnrc_netif_ipv6_wait_for_global_address/main.c b/tests/gnrc_netif_ipv6_wait_for_global_address/main.c new file mode 100644 index 0000000000..30858dd8c6 --- /dev/null +++ b/tests/gnrc_netif_ipv6_wait_for_global_address/main.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2021 ML!PA Consulting GmbH + * + * 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 + * @brief Tests notification for global address + * + * @author Benjamin Valentin + * + * @} + */ + +#include +#include + +#include "embUnit.h" +#include "embUnit/embUnit.h" +#include "net/ipv6.h" +#include "net/gnrc/netif.h" +#include "net/gnrc/netif/raw.h" +#include "net/netdev_test.h" +#include "xtimer.h" + +#define TEST_NETIF_NUMOF 2 +#define TEST_NETIF_PRIO 3 + +static netdev_test_t netdev_test[TEST_NETIF_NUMOF]; +static gnrc_netif_t netif_test[TEST_NETIF_NUMOF]; +static char netif_stack[TEST_NETIF_NUMOF][THREAD_STACKSIZE_DEFAULT]; +static char adder_stack[THREAD_STACKSIZE_DEFAULT]; + +static gnrc_netif_t *_test_netif; +static const ipv6_addr_t _test_addr = {{ 0x20, 0x01, 0x0d, 0xd8, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01 }}; +static void tear_down(void) +{ + if (_test_netif == NULL) { + return; + } + + gnrc_netif_ipv6_addr_remove(_test_netif, &_test_addr); + _test_netif = NULL; +} + +static void *_adder_thread(void *netif) +{ + xtimer_msleep(10); + gnrc_netif_ipv6_addr_add(netif, &_test_addr, 64, + GNRC_NETIF_IPV6_ADDRS_FLAGS_STATE_VALID); + return NULL; +} + +static void _add_delayed_addr(gnrc_netif_t *netif) +{ + _test_netif = netif; + memset(adder_stack, 0, sizeof(adder_stack)); + thread_create(adder_stack, sizeof(adder_stack), + THREAD_PRIORITY_MAIN - 1, + THREAD_CREATE_STACKTEST, + _adder_thread, netif, "add_addr"); +} + +static void _assert_wait_blocks(gnrc_netif_t *add_netif, + gnrc_netif_t *wait_netif, + bool success) +{ + uint32_t now = xtimer_now_usec(); + uint32_t timeout = 20; + + _add_delayed_addr(add_netif); + TEST_ASSERT(gnrc_netif_ipv6_wait_for_global_address(wait_netif, + timeout) == success); + if (success) { + TEST_ASSERT(((xtimer_now_usec() - now) / US_PER_MS) < timeout); + } else { + TEST_ASSERT(((xtimer_now_usec() - now) / US_PER_MS) >= timeout); + } +} + +static void test_wait_timeout(void) +{ + TEST_ASSERT_EQUAL_INT(TEST_NETIF_NUMOF, gnrc_netif_numof()); + TEST_ASSERT(!gnrc_netif_ipv6_wait_for_global_address(NULL, 10)); + TEST_ASSERT(!gnrc_netif_ipv6_wait_for_global_address(&netif_test[0], 10)); +} + +static void test_wait_timeout_other_iface(void) +{ + TEST_ASSERT_EQUAL_INT(TEST_NETIF_NUMOF, gnrc_netif_numof()); + + /* no event when adding addr to other interface */ + _assert_wait_blocks(&netif_test[1], &netif_test[0], false); +} + +static void test_wait_success(void) +{ + /* event when adding addr to specified interface */ + _assert_wait_blocks(&netif_test[0], &netif_test[0], true); +} + +static void test_wait_success_any_iface(void) +{ + /* event when adding addr to any interface */ + _assert_wait_blocks(&netif_test[0], NULL, true); +} + +static Test *embunit_tests_gnrc_netif(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_wait_timeout), + new_TestFixture(test_wait_timeout_other_iface), + new_TestFixture(test_wait_success), + new_TestFixture(test_wait_success_any_iface), + }; + EMB_UNIT_TESTCALLER(tests, NULL, tear_down, fixtures); + + return (Test *)&tests; +} + +static int netdev_get_device_type(netdev_t *dev, void *value, size_t max_len) +{ + (void)dev; + (void)max_len; + const uint16_t type = NETDEV_TYPE_SLIP; + memcpy(value, &type, sizeof(type)); + return sizeof(uint16_t); +} + +static void _setup_mock_netif(netdev_test_t *dev, gnrc_netif_t *netif, + void *stack, size_t stack_size, unsigned prio) +{ + netdev_test_setup(dev, NULL); + netdev_test_set_get_cb(dev, NETOPT_DEVICE_TYPE, netdev_get_device_type); + gnrc_netif_raw_create(netif, stack, stack_size, prio, + "netdev_test", &dev->netdev.netdev); +} + +int main(void) +{ + for (unsigned i = 0; i < TEST_NETIF_NUMOF; ++i) { + _setup_mock_netif(&netdev_test[i], &netif_test[i], + netif_stack[i], sizeof(netif_stack[i]), + TEST_NETIF_PRIO); + } + + TESTS_START(); + TESTS_RUN(embunit_tests_gnrc_netif()); + TESTS_END(); + + return 0; +} diff --git a/tests/gnrc_netif_ipv6_wait_for_global_address/tests/01-run.py b/tests/gnrc_netif_ipv6_wait_for_global_address/tests/01-run.py new file mode 100755 index 0000000000..46ad46ee0e --- /dev/null +++ b/tests/gnrc_netif_ipv6_wait_for_global_address/tests/01-run.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2021 Benjamin Valentin +# +# 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 sys +from testrunner import run + + +def testfunc(child): + child.expect_exact("OK") + + +if __name__ == "__main__": + sys.exit(run(testfunc))