From db463a3373b25d3192ed75f83803a48414a0f27b Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Thu, 6 Feb 2020 15:09:58 +0100 Subject: [PATCH 1/2] gnrc_dhcpv6_client_6lbr: initial import of a 6LBR DHCPv6 client --- Makefile.dep | 4 + sys/auto_init/auto_init.c | 6 + sys/include/net/gnrc/dhcpv6/client/6lbr.h | 62 ++++++++ sys/net/gnrc/Kconfig | 1 + sys/net/gnrc/application_layer/dhcpv6/Kconfig | 26 ++++ .../application_layer/dhcpv6/client_6lbr.c | 139 ++++++++++++++++++ 6 files changed, 238 insertions(+) create mode 100644 sys/include/net/gnrc/dhcpv6/client/6lbr.h create mode 100644 sys/net/gnrc/application_layer/dhcpv6/Kconfig create mode 100644 sys/net/gnrc/application_layer/dhcpv6/client_6lbr.c diff --git a/Makefile.dep b/Makefile.dep index f3cda9959b..93c657c238 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -96,6 +96,10 @@ ifneq (,$(filter gnrc_dhcpv6_client,$(USEMODULE))) USEMODULE += gnrc_sock_udp endif +ifneq (,$(filter gnrc_dhcpv6_client_6lbr,$(USEMODULE))) + USEMODULE += gnrc_dhcpv6_client +endif + ifneq (,$(filter gnrc_uhcpc,$(USEMODULE))) USEMODULE += uhcpc USEMODULE += gnrc_sock_udp diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index 10efdf4d16..1127af2265 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -639,4 +639,10 @@ void auto_init(void) extern void dhcpv6_client_auto_init(void); dhcpv6_client_auto_init(); #endif /* MODULE_AUTO_INIT_DHCPV6_CLIENT */ + +#ifdef MODULE_GNRC_DHCPV6_CLIENT_6LBR + DEBUG("auto_init 6LoWPAN border router DHCPv6 client"); + extern void gnrc_dhcpv6_client_6lbr_init(void); + gnrc_dhcpv6_client_6lbr_init(); +#endif /* MODULE_GNRC_DHCPV6_CLIENT_6LBR */ } diff --git a/sys/include/net/gnrc/dhcpv6/client/6lbr.h b/sys/include/net/gnrc/dhcpv6/client/6lbr.h new file mode 100644 index 0000000000..f622d236ab --- /dev/null +++ b/sys/include/net/gnrc/dhcpv6/client/6lbr.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 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 net_dhcpv6_client_6lbr DHCPv6 client for 6LoWPAN border routers + * @ingroup net_dhcpv6_client + * @brief DHCPv6 client bootstrapping for 6LoWPAN border routers + * @{ + * + * @file + * @brief DHCPv6 client on 6LoWPAN border router definitions + * + * @author Martine S. Lenders + */ +#ifndef NET_GNRC_DHCPV6_CLIENT_6LBR_H +#define NET_GNRC_DHCPV6_CLIENT_6LBR_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Identifier of the upstream interface + * + * Leave 0 (default) to let the client pick the first non-6LoWPAN interface it + * finds + */ +#ifndef CONFIG_GNRC_DHCPV6_CLIENT_6LBR_UPSTREAM +#define CONFIG_GNRC_DHCPV6_CLIENT_6LBR_UPSTREAM (0) +#endif + +/** + * @brief Use static routes to upstream router + * + * If set the border router will be configured to have a default route via + * `fe80::1`. The link-local address `fe80::2` will be added so that the + * upstream router can set a static route for the delegated prefix via that + * address. It is recommended to increase at least @ref + * CONFIG_GNRC_NETIF_IPV6_ADDRS_NUMOF to that end. + */ +#ifdef DOXYGEN +#define CONFIG_GNRC_DHCPV6_CLIENT_6LBR_STATIC_ROUTE +#endif + +/** + * @brief Initializes the DHCPv6 client for 6LoWPAN border router + * + * @note Called by `auto_init` when included + */ +void gnrc_dhcpv6_client_6lbr_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_GNRC_DHCPV6_CLIENT_6LBR_H */ +/** @} */ diff --git a/sys/net/gnrc/Kconfig b/sys/net/gnrc/Kconfig index a940e6d246..eef5446323 100644 --- a/sys/net/gnrc/Kconfig +++ b/sys/net/gnrc/Kconfig @@ -7,6 +7,7 @@ menu "GNRC Network stack" depends on MODULE_GNRC +rsource "application_layer/dhcpv6/Kconfig" rsource "link_layer/lorawan/Kconfig" rsource "netif/Kconfig" rsource "network_layer/ipv6/Kconfig" diff --git a/sys/net/gnrc/application_layer/dhcpv6/Kconfig b/sys/net/gnrc/application_layer/dhcpv6/Kconfig new file mode 100644 index 0000000000..2b55f9dc11 --- /dev/null +++ b/sys/net/gnrc/application_layer/dhcpv6/Kconfig @@ -0,0 +1,26 @@ +menuconfig KCONFIG_MODULE_GNRC_DHCPV6 + bool "Configure GNRC-part of DHCPv6" + depends on MODULE_GNRC_DHCPV6 + help + Configure GNRC-part of DHCPv6 via Kconfig. + +if KCONFIG_MODULE_GNRC_DHCPV6 + +if MODULE_GNRC_DHCPV6_CLIENT_6LBR +config GNRC_DHCPV6_CLIENT_6LBR_UPSTREAM + int "Identifier for the upstream interface of the 6LoWPAN border router" + default 0 + help + Leave 0 to let the client pick the first non-6LoWPAN interface it finds + +config GNRC_DHCPV6_CLIENT_6LBR_STATIC_ROUTE + bool "Use static routes to upstream interface" + help + If set to 1 the border router will be configured to have a default + route via `fe80::1`. The link-local address `fe80::2` will be added so + that the upstream router can set a static route for the delegated + prefix via that address. It is recommended to increase at least @ref + CONFIG_GNRC_NETIF_IPV6_ADDRS_NUMOF to that end. +endif # MODULE_GNRC_DHCPV6_CLIENT_6LBR + +endif # KCONFIG_MODULE_GNRC_DHCPV6 diff --git a/sys/net/gnrc/application_layer/dhcpv6/client_6lbr.c b/sys/net/gnrc/application_layer/dhcpv6/client_6lbr.c new file mode 100644 index 0000000000..bc4fa28959 --- /dev/null +++ b/sys/net/gnrc/application_layer/dhcpv6/client_6lbr.c @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2018-20 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 + */ + +#include + +#include "event.h" +#include "log.h" +#include "net/dhcpv6/client.h" +#include "net/ipv6/addr.h" +#include "net/gnrc.h" +#include "net/gnrc/ipv6/nib/ft.h" +#include "net/gnrc/netif/internal.h" + +#include "net/gnrc/dhcpv6/client/6lbr.h" + +#if IS_USED(MODULE_AUTO_INIT_DHCPV6_CLIENT) +#error "Module `gnrc_dhcpv6_client_6lbr` is mutually exclusive to \ +`auto_init_dhcpv6_client`" +#endif + +static char _stack[DHCPV6_CLIENT_STACK_SIZE]; + +/** + * @brief Find upstream network interface + * + * Either the one network interface configured at compile-time with @ref + * CONFIG_GNRC_DHCPV6_CLIENT_6LBR_UPSTREAM is picked or the first network + * interface that is not a 6LoWPAN interfaces if + * `CONFIG_GNRC_DHCPV6_CLIENT_6LBR_UPSTREAM` is 0. + * + * @return The upstream network interface. + */ +static gnrc_netif_t *_find_upstream_netif(void) +{ + gnrc_netif_t *netif = NULL; + + if (CONFIG_GNRC_DHCPV6_CLIENT_6LBR_UPSTREAM) { + return gnrc_netif_get_by_pid(CONFIG_GNRC_DHCPV6_CLIENT_6LBR_UPSTREAM); + } + while ((netif = gnrc_netif_iter(netif))) { + if (!gnrc_netif_is_6lo(netif)) { + LOG_WARNING("DHCPv6: Selecting interface %d as upstream\n", + netif->pid); + return netif; + } + } + return NULL; +} + +/** + * @brief Configure upstream netif to be in line with configuration script + * + * Set route and link-local address in accordance to + * `dist/tools/ethos/setup_network.sh`. + * + * @note This might not be necessary with a properly set-up DHCPv6 server + * (automatically configures a route for the delegated prefix) and + * upstream router (sends periodic router advertisements). + * + * @param[in] upstream_netif The upstream netif The upstream netif + */ +static void _configure_upstream_netif(gnrc_netif_t *upstream_netif) +{ + if (IS_ACTIVE(CONFIG_GNRC_DHCPV6_CLIENT_6LBR_STATIC_ROUTE)) { + ipv6_addr_t addr = { + .u8 = { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 } + }; + + /* set default route to host machine (as set-up in setup_network.sh) */ + gnrc_ipv6_nib_ft_add(NULL, 0, &addr, upstream_netif->pid, 0); + /* set additional link-local address to provide a well-known next hop + * for static route configuration on the host machine */ + addr.u8[15] = 2; + gnrc_netif_ipv6_addr_add(upstream_netif, &addr, 64, 0); + } +} + +/** + * @brief Configures all 6LoWPAN interfaces to request a 64-bit prefix + */ +static void _configure_dhcpv6_client(void) +{ + gnrc_netif_t *netif = NULL; + while ((netif = gnrc_netif_iter(netif))) { + if (gnrc_netif_is_6lo(netif)) { + dhcpv6_client_req_ia_pd(netif->pid, 64U); + } + } +} + +/** + * @brief The DHCPv6 client thread + */ +static void *_dhcpv6_cl_6lbr_thread(void *args) +{ + event_queue_t event_queue; + gnrc_netif_t *upstream_netif = _find_upstream_netif(); + + (void)args; + if (upstream_netif == NULL) { + LOG_ERROR("DHCPv6: No upstream interface found!\n"); + return NULL; + } + _configure_upstream_netif(upstream_netif); + /* initialize client event queue */ + event_queue_init(&event_queue); + /* initialize DHCPv6 client on border interface */ + dhcpv6_client_init(&event_queue, upstream_netif->pid); + /* configure client to request prefix delegation for WPAN interfaces */ + _configure_dhcpv6_client(); + /* start DHCPv6 client */ + dhcpv6_client_start(); + /* start event loop of DHCPv6 client */ + event_loop(&event_queue); /* never returns */ + return NULL; +} + +void gnrc_dhcpv6_client_6lbr_init(void) +{ + /* start DHCPv6 client thread to request prefix for WPAN */ + thread_create(_stack, DHCPV6_CLIENT_STACK_SIZE, + DHCPV6_CLIENT_PRIORITY, + THREAD_CREATE_STACKTEST, + _dhcpv6_cl_6lbr_thread, NULL, "dhcpv6-client"); +} + +/** @} */ From 8cafcc3ebf2ad6be27f79bbef71308b68a729052 Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Fri, 7 Feb 2020 16:51:42 +0100 Subject: [PATCH 2/2] tests: provide tests for 6LBR DHCPv6 client --- tests/gnrc_dhcpv6_client_6lbr/Kconfig | 10 + tests/gnrc_dhcpv6_client_6lbr/Makefile | 52 ++++++ .../Makefile.board.dep | 6 + tests/gnrc_dhcpv6_client_6lbr/Makefile.ci | 56 ++++++ tests/gnrc_dhcpv6_client_6lbr/README.md | 30 +++ tests/gnrc_dhcpv6_client_6lbr/main.c | 27 +++ tests/gnrc_dhcpv6_client_6lbr/tests/01-run.py | 173 ++++++++++++++++++ 7 files changed, 354 insertions(+) create mode 100644 tests/gnrc_dhcpv6_client_6lbr/Kconfig create mode 100644 tests/gnrc_dhcpv6_client_6lbr/Makefile create mode 100644 tests/gnrc_dhcpv6_client_6lbr/Makefile.board.dep create mode 100644 tests/gnrc_dhcpv6_client_6lbr/Makefile.ci create mode 100644 tests/gnrc_dhcpv6_client_6lbr/README.md create mode 100644 tests/gnrc_dhcpv6_client_6lbr/main.c create mode 100755 tests/gnrc_dhcpv6_client_6lbr/tests/01-run.py diff --git a/tests/gnrc_dhcpv6_client_6lbr/Kconfig b/tests/gnrc_dhcpv6_client_6lbr/Kconfig new file mode 100644 index 0000000000..a45569e9a3 --- /dev/null +++ b/tests/gnrc_dhcpv6_client_6lbr/Kconfig @@ -0,0 +1,10 @@ +if MODULE_ETHOS +config GNRC_DHCPV6_CLIENT_6LBR_STATIC_ROUTE + default y + depends on MODULE_GNRC_DHCPV6_CLIENT_6LBR && KCONFIG_MODULE_GNRC_DHCPV6 +config GNRC_NETIF_IPV6_ADDRS_NUMOF + # CONFIG_GNRC_DHCPV6_CLIENT_6LBR_STATIC_ROUTE=1 requires one more address + # for `fe80::2`. + default 3 + depends on KCONFIG_MODULE_GNRC_NETIF +endif # MODULE_ETHOS diff --git a/tests/gnrc_dhcpv6_client_6lbr/Makefile b/tests/gnrc_dhcpv6_client_6lbr/Makefile new file mode 100644 index 0000000000..486152ac1c --- /dev/null +++ b/tests/gnrc_dhcpv6_client_6lbr/Makefile @@ -0,0 +1,52 @@ +DEVELHELP := 1 +include $(CURDIR)/../Makefile.tests_common + +export TAP ?= tap0 +GNRC_NETIF_NUMOF := 2 + +USEMODULE += auto_init_gnrc_netif +USEMODULE += gnrc_dhcpv6_client_6lbr +USEMODULE += gnrc_netdev_default +USEMODULE += gnrc_pktdump +USEMODULE += gnrc_sixlowpan_border_router_default +USEMODULE += ps +USEMODULE += shell +USEMODULE += shell_commands + +# use Ethernet as link-layer protocol +ifeq (native,$(BOARD)) + TERMFLAGS += -z [::1]:17754 +else + ETHOS_BAUDRATE ?= 115200 + CFLAGS += -DETHOS_BAUDRATE=$(ETHOS_BAUDRATE) + TERMDEPS += ethos + TERMPROG ?= sudo $(RIOTTOOLS)/ethos/ethos + TERMFLAGS ?= $(TAP) $(PORT) $(ETHOS_BAUDRATE) + STATIC_ROUTES ?= 1 +endif + +# The test requires some setup and to be run as root +# So it cannot currently be run on CI +TEST_ON_CI_BLACKLIST += all + +# As there is an 'app.config' we want to explicitly disable Kconfig by setting +# the variable to empty +SHOULD_RUN_KCONFIG ?= + +include $(RIOTBASE)/Makefile.include + +ifndef CONFIG_GNRC_DHCPV6_CLIENT_6LBR_STATIC_ROUTE +ifeq (1,$(STATIC_ROUTES)) + CFLAGS += -DCONFIG_GNRC_DHCPV6_CLIENT_6LBR_STATIC_ROUTE=1 + # CONFIG_GNRC_DHCPV6_CLIENT_6LBR_STATIC_ROUTE=1 requires one more address for + # `fe80::2`. + CFLAGS += -DCONFIG_GNRC_NETIF_IPV6_ADDRS_NUMOF=3 +endif +endif + +ifeq (,$(filter native,$(BOARD))) +.PHONY: ethos + +ethos: + $(Q)env -u CC -u CFLAGS make -C $(RIOTBASE)/dist/tools/ethos +endif diff --git a/tests/gnrc_dhcpv6_client_6lbr/Makefile.board.dep b/tests/gnrc_dhcpv6_client_6lbr/Makefile.board.dep new file mode 100644 index 0000000000..c20e0e5e31 --- /dev/null +++ b/tests/gnrc_dhcpv6_client_6lbr/Makefile.board.dep @@ -0,0 +1,6 @@ +# Put board specific dependencies here +ifeq (native,$(BOARD)) + USEMODULE += socket_zep +else + USEMODULE += stdio_ethos +endif diff --git a/tests/gnrc_dhcpv6_client_6lbr/Makefile.ci b/tests/gnrc_dhcpv6_client_6lbr/Makefile.ci new file mode 100644 index 0000000000..914efb276a --- /dev/null +++ b/tests/gnrc_dhcpv6_client_6lbr/Makefile.ci @@ -0,0 +1,56 @@ +BOARD_INSUFFICIENT_MEMORY := \ + airfy-beacon \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega1284p \ + atmega328p \ + b-l072z-lrwan1 \ + blackpill-128kib \ + blackpill \ + bluepill-128kib \ + bluepill \ + calliope-mini \ + cc2650-launchpad \ + cc2650stk \ + derfmega128 \ + hifive1 \ + hifive1b \ + i-nucleo-lrwan1 \ + lsn50 \ + maple-mini \ + mega-xplained \ + microbit \ + microduino-corerf \ + msb-430 \ + msb-430h \ + nrf51dongle \ + nrf6310 \ + nucleo-f030r8 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-f070rb \ + nucleo-f072rb \ + nucleo-f103rb \ + nucleo-f302r8 \ + nucleo-f303k8 \ + nucleo-f334r8 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + nucleo-l073rz \ + opencm904 \ + saml10-xpro \ + saml11-xpro \ + spark-core \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32l0538-disco \ + telosb \ + waspmote-pro \ + wsn430-v1_3b \ + wsn430-v1_4 \ + yunjia-nrf51822 \ + z1 \ + # diff --git a/tests/gnrc_dhcpv6_client_6lbr/README.md b/tests/gnrc_dhcpv6_client_6lbr/README.md new file mode 100644 index 0000000000..4f1cec624f --- /dev/null +++ b/tests/gnrc_dhcpv6_client_6lbr/README.md @@ -0,0 +1,30 @@ +# `gnrc_dhcpv6_client_6lbr` test + +This test utilizes [scapy] to test the DHCPv6 client configuration for a 6LoWPAN +border router. + +To test, compile and flash the application to any board of your liking (since +`ethos` is used to communicate with non-native boards it really doesn't matter +as long as the application fits). + +``` +make flash +``` + +And run the tests using + +``` +sudo make test +``` + +Note that root privileges are required since `scapy` needs to construct Ethernet +frames to properly communicate over the TAP interface. + +The tests succeeds if you see the string `SUCCESS`. + +If any problems are encountered (i.e. if the test prints the sting `FAILED`), +set the echo parameter in the `run()` function at the bottom of the test script +(tests/01-run.py) to `True`. The test script will then offer a more detailed +output. + +[scapy]: https://scapy.readthedocs.io/en/latest/ diff --git a/tests/gnrc_dhcpv6_client_6lbr/main.c b/tests/gnrc_dhcpv6_client_6lbr/main.c new file mode 100644 index 0000000000..e0eeb3ec91 --- /dev/null +++ b/tests/gnrc_dhcpv6_client_6lbr/main.c @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 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 "shell.h" + +int main(void) +{ + 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_dhcpv6_client_6lbr/tests/01-run.py b/tests/gnrc_dhcpv6_client_6lbr/tests/01-run.py new file mode 100755 index 0000000000..21853385a0 --- /dev/null +++ b/tests/gnrc_dhcpv6_client_6lbr/tests/01-run.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2018 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 os +import pexpect +import random +import sys +import time + +from scapy.all import AsyncSniffer, sendp, Ether, IPv6, UDP +from scapy.all import DHCP6_Solicit, DHCP6_Advertise, DHCP6_Request, DHCP6_Reply +from scapy.all import DHCP6OptClientId, DHCP6OptServerId, DHCP6OptIA_PD +from scapy.all import DUID_LL, DHCP6OptIAPrefix +from testrunner import run + + +TIMEOUT = 1 + + +def get_upstream_netif(child): + child.sendline("ifconfig") + child.sendline("help") # workaround to spot end of ifconfig output + candidate = None + while True: # Search for an interface that does _not_ contain 6LO flag + if candidate is None: + child.expect(r"Iface\s+([^\s]+)\s+") + candidate = child.match.group(1) + res = child.expect([r"\b6LO\b", r"Iface\s+([^\s]+)\s+", "Command", + pexpect.TIMEOUT], timeout=.2) + if res > 0: + break + candidate = None + # wait for a line in "help" + child.expect("reboot") + return candidate + + +def get_downstream_netif(child): + child.sendline("ifconfig") + child.sendline("help") # workaround to spot end of ifconfig output + candidate = None + while True: # Search for an interface that does _not_ contain 6LO flag + if candidate is None: + child.expect(r"Iface\s+([^\s]+)\s+") + candidate = child.match.group(1) + res = child.expect([r"\b6LO\b", r"Iface\s+([^\s]+)\s+", "Command", + pexpect.TIMEOUT], timeout=.2) + if res == 0: + break + elif res == 1: + candidate = child.match.group(1) + elif res == 2: + break + else: + candidate = None + # wait for a line in "help" + child.expect("reboot") + return candidate + + +def get_hwaddrs(child, netif): + hwaddrs = [] + child.sendline("ifconfig {}".format(netif)) + child.expect(r"HWaddr:\s+(([A-Fa-f0-9]{2}:?)+)\s") + hwaddrs.append(child.match.group(1).lower()) + if len(hwaddrs[0]) == 5: # short address + res = child.expect([pexpect.TIMEOUT, + r"Long HWaddr:\s+(([A-Fa-f0-9]{2}:?)+)\s"]) + if res > 0: + hwaddrs.append(child.match.group(1).lower()) + return hwaddrs + + +def start_sniffer(iface, count=None, stop_filter=None): + sniffer = AsyncSniffer( + iface=iface, + filter="udp and dst port 547", + count=count, + stop_filter=stop_filter, + ) + sniffer.start() + return sniffer + + +def wait_for_dhcpv6_pkt(iface, sniffer=None, timeout=5): + if sniffer is None: + sniffer = start_sniffer(iface, count=1) + sniffer.join(timeout=timeout) + if sniffer.results is None: + raise TimeoutError("Sniffing for DHCPv6 traffic timed out") + return [p for p in sniffer.results + # filter out packets only belonging to stop_filter if it existed + if sniffer.kwargs.get("stop_filter") is None or + sniffer.kwargs["stop_filter"](p)][-1] + + +def build_reply_headers(pkt): + src_ether = pkt[Ether].src + src_ip = pkt[IPv6].src + sport = pkt[UDP].sport + dport = pkt[UDP].dport + return Ether(dst=src_ether) / IPv6(dst=src_ip) / \ + UDP(sport=dport, dport=sport) + + +def testfunc(child): + iface = os.environ["TAP"] + + pkt = wait_for_dhcpv6_pkt(iface) + # the packet was a solicit + assert DHCP6_Solicit in pkt + # check if the sender is the upstream interface of the node + upstream_netif = get_upstream_netif(child) + print(upstream_netif) + upstream_hwaddrs = get_hwaddrs(child, upstream_netif) + assert DHCP6OptClientId in pkt and DUID_LL in pkt[DHCP6OptClientId].duid + assert pkt[DHCP6OptClientId].duid[DUID_LL].lladdr in upstream_hwaddrs + # and it is asking for a prefix delegation + assert DHCP6OptIA_PD in pkt + + # reply to solicit with advertise and a prefix provided + trid = pkt[DHCP6_Solicit].trid + srv_duid = "aa:bb:cc:dd:ee:ff" + cli_id = DHCP6OptClientId(duid=pkt[DHCP6OptClientId].duid) + srv_id = DHCP6OptServerId(duid=DUID_LL(lladdr=srv_duid)) + prefix = "2001:db8:{:x}:{:x}::".format( + random.randint(0, 0xffff), + random.randint(0, 0xffff) + ) + ia_pd = DHCP6OptIA_PD(T1=12000, T2=13000, iaid=pkt[DHCP6OptIA_PD].iaid, + iapdopt=[ + DHCP6OptIAPrefix(preflft=14000, validlft=15000, + prefix=prefix, plen=64)]) + # start sniffer to catch incoming request + sniffer = start_sniffer(iface, + stop_filter=lambda pkt: DHCP6_Request in pkt) + sendp(build_reply_headers(pkt) / DHCP6_Advertise(trid=trid) / + cli_id / srv_id / ia_pd, iface=iface, verbose=False) + + # wait for request + pkt = wait_for_dhcpv6_pkt(iface, sniffer) + # the packet was indeed a request + assert DHCP6_Request in pkt + # and from the client + assert DHCP6OptClientId in pkt and DUID_LL in pkt[DHCP6OptClientId].duid + assert pkt[DHCP6OptClientId].duid[DUID_LL].lladdr in upstream_hwaddrs + # and it is trying to talk to this server + assert DHCP6OptServerId in pkt and DUID_LL in pkt[DHCP6OptServerId].duid + assert pkt[DHCP6OptServerId].duid[DUID_LL].lladdr == srv_duid + # and is still asking for a prefix delegation + assert DHCP6OptIA_PD in pkt + + # reply to request with reply and a prefix provided + trid = pkt[DHCP6_Request].trid + sendp(build_reply_headers(pkt) / DHCP6_Reply(trid=trid) / + cli_id / srv_id / ia_pd, iface=iface, verbose=False) + time.sleep(1) + + # check if global address was configured + child.sendline("ifconfig {}".format(get_downstream_netif(child))) + # remove one trailing ':' from prefix just to be safe ;-) + child.expect(r"inet6 addr:\s+{}[0-9a-fA-F:]+\s" + .format(prefix[:-1])) + print("SUCCESS") + + +if __name__ == "__main__": + sys.exit(run(testfunc, timeout=TIMEOUT, echo=True))