diff --git a/examples/gcoap/Makefile b/examples/gcoap/Makefile index 52c4dcdc71..6fe8594a3f 100644 --- a/examples/gcoap/Makefile +++ b/examples/gcoap/Makefile @@ -22,6 +22,7 @@ USEMODULE += gnrc_icmpv6_echo # Required by gcoap example USEMODULE += od USEMODULE += fmt +USEMODULE += netutils # Add also the shell, some shell commands USEMODULE += shell USEMODULE += shell_commands diff --git a/examples/gcoap/gcoap_cli.c b/examples/gcoap/gcoap_cli.c index c38a7f90c7..f089a65c7b 100644 --- a/examples/gcoap/gcoap_cli.c +++ b/examples/gcoap/gcoap_cli.c @@ -24,6 +24,7 @@ #include #include #include "net/gcoap.h" +#include "net/utils.h" #include "od.h" #include "fmt.h" @@ -254,40 +255,17 @@ static ssize_t _riot_board_handler(coap_pkt_t *pdu, uint8_t *buf, size_t len, vo } static bool _parse_endpoint(sock_udp_ep_t *remote, - char *addr_str, char *port_str) + const char *addr_str, const char *port_str) { - ipv6_addr_t addr; - remote->family = AF_INET6; + netif_t *netif; - /* parse for interface */ - char *iface = ipv6_addr_split_iface(addr_str); - if (!iface) { - if (gnrc_netif_numof() == 1) { - /* assign the single interface found in gnrc_netif_numof() */ - remote->netif = (uint16_t)gnrc_netif_iter(NULL)->pid; - } - else { - remote->netif = SOCK_ADDR_ANY_NETIF; - } - } - else { - int pid = atoi(iface); - if (gnrc_netif_get_by_pid(pid) == NULL) { - puts("gcoap_cli: interface not valid"); - return false; - } - remote->netif = pid; - } - /* parse destination address */ - if (ipv6_addr_from_str(&addr, addr_str) == NULL) { + /* parse hostname */ + if (netutils_get_ipv6((ipv6_addr_t *)&remote->addr, &netif, addr_str) < 0) { puts("gcoap_cli: unable to parse destination address"); return false; } - if ((remote->netif == SOCK_ADDR_ANY_NETIF) && ipv6_addr_is_link_local(&addr)) { - puts("gcoap_cli: must specify interface for link local target"); - return false; - } - memcpy(&remote->addr.ipv6[0], &addr.u8[0], sizeof(addr.u8)); + remote->netif = netif ? netif_get_id(netif) : SOCK_ADDR_ANY_NETIF; + remote->family = AF_INET6; /* parse port */ remote->port = atoi(port_str); diff --git a/examples/gcoap_dtls/Makefile b/examples/gcoap_dtls/Makefile index 8d5cfdc916..bd7e6f7cfe 100644 --- a/examples/gcoap_dtls/Makefile +++ b/examples/gcoap_dtls/Makefile @@ -22,6 +22,7 @@ USEMODULE += gnrc_icmpv6_echo # Required by gcoap example USEMODULE += od USEMODULE += fmt +USEMODULE += netutils # Add also the shell, some shell commands USEMODULE += shell USEMODULE += shell_commands diff --git a/examples/gnrc_networking/Makefile b/examples/gnrc_networking/Makefile index aa06541d76..a59b776599 100644 --- a/examples/gnrc_networking/Makefile +++ b/examples/gnrc_networking/Makefile @@ -23,6 +23,7 @@ USEMODULE += auto_init_gnrc_rpl USEMODULE += gnrc_pktdump # Additional networking modules that can be dropped if not needed USEMODULE += gnrc_icmpv6_echo +USEMODULE += netutils # Add also the shell, some shell commands USEMODULE += shell USEMODULE += shell_commands diff --git a/examples/gnrc_networking/udp.c b/examples/gnrc_networking/udp.c index f7ae44df6d..ad222e5135 100644 --- a/examples/gnrc_networking/udp.c +++ b/examples/gnrc_networking/udp.c @@ -28,6 +28,7 @@ #include "net/gnrc/netif/hdr.h" #include "net/gnrc/udp.h" #include "net/gnrc/pktdump.h" +#include "net/utils.h" #include "timex.h" #include "utlist.h" #if IS_USED(MODULE_ZTIMER_MSEC) @@ -43,21 +44,12 @@ static gnrc_netreg_entry_t server = static void send(char *addr_str, char *port_str, char *data, unsigned int num, unsigned int delay) { - gnrc_netif_t *netif = NULL; - char *iface; + netif_t *netif; uint16_t port; ipv6_addr_t addr; - iface = ipv6_addr_split_iface(addr_str); - if ((!iface) && (gnrc_netif_numof() == 1)) { - netif = gnrc_netif_iter(NULL); - } - else if (iface) { - netif = gnrc_netif_get_by_pid(atoi(iface)); - } - /* parse destination address */ - if (ipv6_addr_from_str(&addr, addr_str) == NULL) { + if (netutils_get_ipv6(&addr, &netif, addr_str) < 0) { puts("Error: unable to parse destination address"); return; } @@ -97,7 +89,8 @@ static void send(char *addr_str, char *port_str, char *data, unsigned int num, if (netif != NULL) { gnrc_pktsnip_t *netif_hdr = gnrc_netif_hdr_build(NULL, 0, NULL, 0); - gnrc_netif_hdr_set_netif(netif_hdr->data, netif); + gnrc_netif_hdr_set_netif(netif_hdr->data, + container_of(netif, gnrc_netif_t, netif)); ip = gnrc_pkt_prepend(ip, netif_hdr); } /* send packet */ diff --git a/sys/Makefile b/sys/Makefile index eade1daea3..50a11546f5 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -113,6 +113,9 @@ endif ifneq (,$(filter netstats_neighbor,$(USEMODULE))) DIRS += net/netstats endif +ifneq (,$(filter netutils,$(USEMODULE))) + DIRS += net/netutils +endif ifneq (,$(filter sema,$(USEMODULE))) DIRS += sema endif diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 8a4322f76f..b469555e15 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -185,6 +185,10 @@ ifneq (,$(filter od_string,$(USEMODULE))) USEMODULE += od endif +ifneq (,$(filter netutils,$(USEMODULE))) + USEMODULE += ipv6_addr +endif + ifneq (,$(filter newlib_gnu_source,$(USEMODULE))) FEATURES_REQUIRED += newlib endif @@ -252,6 +256,10 @@ ifneq (,$(filter shell_commands,$(USEMODULE))) USEMODULE += posix_inet endif + ifneq (,$(filter gnrc_icmpv6_echo,$(USEMODULE))) + USEMODULE += netutils + endif + ifneq (,$(filter nimble_netif,$(USEMODULE))) USEMODULE += nimble_scanner USEMODULE += nimble_scanlist diff --git a/sys/include/net/utils.h b/sys/include/net/utils.h new file mode 100644 index 0000000000..662c3704b0 --- /dev/null +++ b/sys/include/net/utils.h @@ -0,0 +1,52 @@ +/* + * 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. + */ + +/** + * @defgroup net_utils Network helper functions + * @ingroup net + * @brief Common network helper functions + * @{ + * + * @file + * @brief Common network interface API definitions + * + * @author Benjamin Valentin + */ + +#ifndef NET_UTILS_H +#define NET_UTILS_H + +#include +#include + +#include "net/ipv6/addr.h" +#include "net/netif.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Parse an IPv6 address / hostname string. + * If the @ref net_sock_dns module is used, this will + * attempt to resolve hostnames via DNS to IPv6 addresses. + * + * @param[out] addr IPv6 address of the host + * @param[out] netif Interface if address is link-local + * @param[in] hostname IPv6 address string or hostname + * + * @return 0 on success, error otherwise + */ +int netutils_get_ipv6(ipv6_addr_t *addr, netif_t **netif, const char *hostname); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_UTILS_H */ +/** @} */ diff --git a/sys/net/netutils/Makefile b/sys/net/netutils/Makefile new file mode 100644 index 0000000000..dc07bd13e5 --- /dev/null +++ b/sys/net/netutils/Makefile @@ -0,0 +1,3 @@ +MODULE = netutils + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/netutils/util.c b/sys/net/netutils/util.c new file mode 100644 index 0000000000..5fbaaabd1c --- /dev/null +++ b/sys/net/netutils/util.c @@ -0,0 +1,78 @@ +/* + * 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. + */ + +/** + * @{ + * + * @file + * @author Benjamin Valentin + */ + +#include +#include +#include + +#include "net/utils.h" +#ifdef MODULE_SOCK_DNS +#include "net/af.h" +#include "net/sock/dns.h" +#endif + +/* get the next netif, returns true if there are more */ +static bool _netif_get(netif_t **current_netif) +{ + netif_t *netif = *current_netif; + netif = netif_iter(netif); + + *current_netif = netif; + return netif_iter(netif); +} + +int netutils_get_ipv6(ipv6_addr_t *addr, netif_t **netif, const char *hostname) +{ + *netif = NULL; + + if (hostname == NULL) { + return -EINVAL; + } + +#ifdef MODULE_SOCK_DNS + /* hostname is not an IPv6 address */ + if (strchr(hostname, ':') == NULL) { + int res = sock_dns_query(hostname, addr, AF_INET6); + if (res < 0) { + return res; + } + return 0; + } +#endif + + /* search for interface ID */ + size_t len = strlen(hostname); + char *iface = strchr(hostname, '%'); + if (iface) { + *netif = netif_get_by_id(atoi(iface + 1)); + len -= strlen(iface); + + if (*netif == NULL) { + return -EINVAL; + } + } + /* preliminary select the first interface */ + else if (_netif_get(netif)) { + /* don't take it if there is more than one interface */ + *netif = NULL; + } + + if (ipv6_addr_from_buf(addr, hostname, len) == NULL) { + return -EINVAL; + } + + return 0; +} +/** @} */ diff --git a/sys/shell/commands/sc_gnrc_icmpv6_echo.c b/sys/shell/commands/sc_gnrc_icmpv6_echo.c index 8873274ff5..2b368ece93 100644 --- a/sys/shell/commands/sc_gnrc_icmpv6_echo.c +++ b/sys/shell/commands/sc_gnrc_icmpv6_echo.c @@ -37,11 +37,9 @@ #ifdef MODULE_GNRC_IPV6_NIB #include "net/gnrc/ipv6/nib/nc.h" #endif -#ifdef MODULE_SOCK_DNS -#include "net/sock/dns.h" -#endif #include "net/icmpv6.h" #include "net/ipv6.h" +#include "net/utils.h" #include "timex.h" #include "unaligned.h" #include "utlist.h" @@ -167,16 +165,6 @@ static void _usage(char *cmdname) "of any responses, otherwise wait for two RTTs"); } -/* get the next netif, returns true if there are more */ -static bool _netif_get(gnrc_netif_t **current_netif) -{ - gnrc_netif_t *netif = *current_netif; - netif = gnrc_netif_iter(netif); - - *current_netif = netif; - return !gnrc_netif_highlander() && gnrc_netif_iter(netif); -} - static int _configure(int argc, char **argv, _ping_data_t *data) { char *cmdname = argv[0]; @@ -188,27 +176,11 @@ static int _configure(int argc, char **argv, _ping_data_t *data) if (arg[0] != '-') { data->hostname = arg; -#ifdef MODULE_SOCK_DNS - if (strchr(data->hostname, ':') == NULL && - sock_dns_query(data->hostname, &data->host, AF_INET6) > 0) { - res = 0; - continue; - } -#endif - char *iface = ipv6_addr_split_iface(data->hostname); - if (iface) { - data->netif = gnrc_netif_get_by_pid(atoi(iface)); - } - /* preliminary select the first interface */ - else if (_netif_get(&data->netif)) { - /* don't take it if there is more than one interface */ - data->netif = NULL; - } - if (ipv6_addr_from_str(&data->host, data->hostname) == NULL) { + res = netutils_get_ipv6(&data->host, (netif_t **)&data->netif, arg); + if (res) { break; } - res = 0; } else { switch (arg[1]) { diff --git a/tests/netutils/Makefile b/tests/netutils/Makefile new file mode 100644 index 0000000000..10c661b057 --- /dev/null +++ b/tests/netutils/Makefile @@ -0,0 +1,14 @@ +include ../Makefile.tests_common + +USEMODULE += netutils +USEMODULE += netif +USEMODULE += embunit + +# make sure we have an implementation of sock_types.h +USEMODULE += gnrc_sock_udp +USEMODULE += gnrc_ipv6 + +# pretend to include sock_dns +CFLAGS += -DMODULE_SOCK_DNS=1 + +include $(RIOTBASE)/Makefile.include diff --git a/tests/netutils/Makefile.ci b/tests/netutils/Makefile.ci new file mode 100644 index 0000000000..9f30089d46 --- /dev/null +++ b/tests/netutils/Makefile.ci @@ -0,0 +1,28 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega328p-xplained-mini \ + atmega328p \ + atxmega-a1u-xpro \ + bluepill-stm32f030c8 \ + i-nucleo-lrwan1 \ + msb-430 \ + msb-430h \ + nucleo-f030r8 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-l011k4 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + samd10-xmini \ + slstk3400a \ + stk3200 \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32l0538-disco \ + telosb \ + waspmote-pro \ + # diff --git a/tests/netutils/main.c b/tests/netutils/main.c new file mode 100644 index 0000000000..728427f8f4 --- /dev/null +++ b/tests/netutils/main.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) 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. + */ + +/** + * @{ + * + * @file + * + * @author Benjamin Valentin + */ +#include +#include + +#include "embUnit.h" + +#include "net/gnrc/netif.h" +#include "net/utils.h" + +static gnrc_netif_t dummy_netif[2]; + +static void test_ipv6_addr_from_str__one_colon_start(void) +{ + ipv6_addr_t address; + netif_t *netif; + + TEST_ASSERT_EQUAL_INT(netutils_get_ipv6(&address, &netif, ":ff::1"), -EINVAL); +} + +static void test_ipv6_addr_from_str__three_colons(void) +{ + ipv6_addr_t address; + netif_t *netif; + + TEST_ASSERT_EQUAL_INT(netutils_get_ipv6(&address, &netif, "ff02:::1"), -EINVAL); +} + +static void test_ipv6_addr_from_str__illegal_chars(void) +{ + ipv6_addr_t address; + netif_t *netif; + + TEST_ASSERT_EQUAL_INT(netutils_get_ipv6(&address, &netif, ":-D"), -EINVAL); +} + +static void test_ipv6_addr_from_str__addr_NULL(void) +{ + ipv6_addr_t address; + netif_t *netif; + + TEST_ASSERT_EQUAL_INT(netutils_get_ipv6(&address, &netif, NULL), -EINVAL); +} + +static void test_ipv6_addr_from_str__address_NULL(void) +{ + TEST_ASSERT_NULL(ipv6_addr_from_str(NULL, "::")); +} + +static void test_ipv6_addr_from_str__success(void) +{ + static const ipv6_addr_t a = { { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + } + }; + ipv6_addr_t address; + netif_t *netif; + + TEST_ASSERT_EQUAL_INT(netutils_get_ipv6(&address, &netif, "1:203:405:607:809:a0b:c0d:e0f"), 0); + TEST_ASSERT(ipv6_addr_equal(&a, &address)); + TEST_ASSERT_NULL(netif); +} + +static void test_ipv6_addr_from_str__success2(void) +{ + static const ipv6_addr_t a = { { + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + } + }; + ipv6_addr_t address; + netif_t *netif; + + TEST_ASSERT_EQUAL_INT(netutils_get_ipv6(&address, &netif, "fe80::f8f9:fafb:fcfd:feff%1"), 0); + TEST_ASSERT_EQUAL_INT((uintptr_t)netif, (uintptr_t)&dummy_netif[1]); + TEST_ASSERT(ipv6_addr_equal(&a, &address)); +} + +static void test_ipv6_addr_from_str__invalid_interface(void) +{ + ipv6_addr_t address; + netif_t *netif; + + TEST_ASSERT_EQUAL_INT(netutils_get_ipv6(&address, &netif, "fe80::f8f9:fafb:fcfd:feff%3"), -EINVAL); +} + +static void test_ipv6_addr_from_str__success4(void) +{ + static const ipv6_addr_t a = { { + 0x26, 0x06, 0x28, 0x00, 0x02, 0x20, 0x00, 0x01, + 0x02, 0x48, 0x18, 0x93, 0x25, 0xc8, 0x19, 0x46 + } + }; + ipv6_addr_t address; + netif_t *netif; + + TEST_ASSERT_EQUAL_INT(netutils_get_ipv6(&address, &netif, "example.com"), 0); + TEST_ASSERT(ipv6_addr_equal(&a, &address)); +} + +static void test_ipv6_addr_from_str__success5(void) +{ + static const ipv6_addr_t a = IPV6_ADDR_LOOPBACK; + ipv6_addr_t address; + + TEST_ASSERT_NOT_NULL(ipv6_addr_from_str(&address, "::1")); + TEST_ASSERT(ipv6_addr_equal(&a, &address)); +} + +Test *tests_netutils_tests(void) +{ + for (unsigned i = 0; i < ARRAY_SIZE(dummy_netif); ++i) { + netif_register(&dummy_netif[i].netif); + dummy_netif[i].pid = i; + } + + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_ipv6_addr_from_str__one_colon_start), + new_TestFixture(test_ipv6_addr_from_str__three_colons), + new_TestFixture(test_ipv6_addr_from_str__illegal_chars), + new_TestFixture(test_ipv6_addr_from_str__addr_NULL), + new_TestFixture(test_ipv6_addr_from_str__address_NULL), + new_TestFixture(test_ipv6_addr_from_str__success), + new_TestFixture(test_ipv6_addr_from_str__success2), + new_TestFixture(test_ipv6_addr_from_str__invalid_interface), + new_TestFixture(test_ipv6_addr_from_str__success4), + new_TestFixture(test_ipv6_addr_from_str__success5), + }; + + EMB_UNIT_TESTCALLER(ipv6_addr_tests, NULL, NULL, fixtures); + + return (Test *)&ipv6_addr_tests; +} + +int main(void) +{ + TESTS_START(); + TESTS_RUN(tests_netutils_tests()); + TESTS_END(); + + return 0; +} +/** @} */ diff --git a/tests/netutils/mock_dns.c b/tests/netutils/mock_dns.c new file mode 100644 index 0000000000..e8eb37ffb3 --- /dev/null +++ b/tests/netutils/mock_dns.c @@ -0,0 +1,43 @@ +/* + * 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. + */ + +/** + * @{ + * + * @file + * @brief Mock implementation of sock_dns + * + * @author Benjamin Valentin + */ +#include + +#include "net/af.h" +#include "net/ipv6/addr.h" +#include "net/sock/dns.h" + +int sock_dns_query(const char *domain_name, void *addr_out, int family) +{ + const ipv6_addr_t a = { { + 0x26, 0x06, 0x28, 0x00, 0x02, 0x20, 0x00, 0x01, + 0x02, 0x48, 0x18, 0x93, 0x25, 0xc8, 0x19, 0x46 + } + }; + + + if (family != AF_INET6) { + return -EAFNOSUPPORT; + } + + if (strcmp(domain_name, "example.com")) { + return -ENOTSUP; + } + + memcpy(addr_out, &a, sizeof(a)); + return 0; +} +/** @} */ diff --git a/tests/netutils/tests/01-run.py b/tests/netutils/tests/01-run.py new file mode 100755 index 0000000000..8b4e23f2e7 --- /dev/null +++ b/tests/netutils/tests/01-run.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2017 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 sys +from testrunner import run_check_unittests + + +if __name__ == "__main__": + sys.exit(run_check_unittests()) diff --git a/tests/riotboot_flashwrite/Makefile.ci b/tests/riotboot_flashwrite/Makefile.ci index 56d283cfb9..72dd2925fb 100644 --- a/tests/riotboot_flashwrite/Makefile.ci +++ b/tests/riotboot_flashwrite/Makefile.ci @@ -14,6 +14,8 @@ BOARD_INSUFFICIENT_MEMORY := \ nucleo-f302r8 \ nucleo-f303k8 \ nucleo-f334r8 \ + nucleo-g070rb \ + nucleo-g071rb \ nucleo-l031k6 \ nucleo-l053r8 \ saml10-xpro \