diff --git a/.github/workflows/simulation-1.2.yml b/.github/workflows/simulation-1.2.yml index 4eac3e682..b4842eaa3 100644 --- a/.github/workflows/simulation-1.2.yml +++ b/.github/workflows/simulation-1.2.yml @@ -249,6 +249,10 @@ jobs: MULTIPLY: 1 PYTHONUNBUFFERED: 1 VERBOSE: 1 + # The border routing and DUA feature can coexist, but current wireshark + # packet verification can't handle it because of the order of context ID + # of OMR prefix and Domain prefix is not deterministic. + BORDER_ROUTING: 0 steps: - uses: actions/checkout@v2 with: @@ -294,6 +298,61 @@ jobs: name: cov-thread-1-2-backbone path: tmp/coverage.info + thread-border-routing: + runs-on: ubuntu-20.04 + env: + REFERENCE_DEVICE: 1 + VIRTUAL_TIME: 0 + PACKET_VERIFICATION: 1 + THREAD_VERSION: 1.2 + INTER_OP: 1 + COVERAGE: 1 + MULTIPLY: 1 + PYTHONUNBUFFERED: 1 + VERBOSE: 1 + steps: + - uses: actions/checkout@v2 + - name: Build OTBR Docker + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + run: | + ./script/test build_otbr_docker + - name: Bootstrap + run: | + sudo rm /etc/apt/sources.list.d/* && sudo apt-get update + sudo apt-get --no-install-recommends install -y python3-setuptools python3-wheel ninja-build socat lcov + python3 -m pip install -r tests/scripts/thread-cert/requirements.txt + - name: Build + run: | + ./script/test build + - name: Get Thread-Wireshark + run: | + ./script/test get_thread_wireshark + - name: Run + run: | + export CI_ENV="$(bash <(curl -s https://codecov.io/env)) -e GITHUB_ACTIONS -e COVERAGE" + echo "CI_ENV=${CI_ENV}" + sudo -E ./script/test cert_suite ./tests/scripts/thread-cert/border_routing/*.py || (sudo chmod a+r *.log *.json *.pcap && false) + - uses: actions/upload-artifact@v2 + with: + name: cov-thread-border-routing-docker + path: /tmp/coverage/ + - uses: actions/upload-artifact@v2 + if: ${{ failure() }} + with: + name: thread-border-routing-results + path: | + *.pcap + *.json + *.log + - name: Generate Coverage + run: | + ./script/test generate_coverage gcc + - uses: actions/upload-artifact@v2 + with: + name: cov-thread-border-routing + path: tmp/coverage.info + upload-coverage: needs: - thread-1-2 @@ -301,6 +360,7 @@ jobs: - packet-verification-1-1-on-1-2 - expects - thread-1-2-backbone + - thread-border-routing runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 diff --git a/Android.mk b/Android.mk index 04ee0562a..3ad773e52 100644 --- a/Android.mk +++ b/Android.mk @@ -186,6 +186,9 @@ LOCAL_SRC_FILES := \ src/core/backbone_router/bbr_manager.cpp \ src/core/backbone_router/multicast_listeners_table.cpp \ src/core/backbone_router/ndproxy_table.cpp \ + src/core/border_router/infra_if_platform.cpp \ + src/core/border_router/router_advertisement.cpp \ + src/core/border_router/routing_manager.cpp \ src/core/coap/coap.cpp \ src/core/coap/coap_message.cpp \ src/core/coap/coap_secure.cpp \ diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 22f338b9f..cb1b00cb4 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -72,6 +72,11 @@ if(OT_BORDER_ROUTER) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE=1") endif() +option(OT_BORDER_ROUTING "enable (duckhorn) border routing support") +if(OT_BORDER_ROUTING) + target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE=1") +endif() + if(NOT OT_EXTERNAL_MBEDTLS) set(OT_MBEDTLS mbedtls) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_ENABLE_BUILTIN_MBEDTLS=1") @@ -290,6 +295,7 @@ if(OT_FULL_LOGS) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_API=1") target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_ARP=1") target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_BBR=1") + target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_BR=1") target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_CLI=1") target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_COAP=1") target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_DUA=1") diff --git a/etc/gn/openthread.gni b/etc/gn/openthread.gni index e69261201..19b8ace79 100644 --- a/etc/gn/openthread.gni +++ b/etc/gn/openthread.gni @@ -90,6 +90,9 @@ if (openthread_enable_core_config_args) { # Enable border router support openthread_config_border_router_enable = false + # Enable border routing support + openthread_config_border_routing_enable = false + # Enable channel manager support openthread_config_channel_manager_enable = false diff --git a/examples/common-switches.mk b/examples/common-switches.mk index b1cc708d0..a3050ab32 100644 --- a/examples/common-switches.mk +++ b/examples/common-switches.mk @@ -32,6 +32,7 @@ BACKBONE_ROUTER ?= 0 BIG_ENDIAN ?= 0 BORDER_AGENT ?= 0 BORDER_ROUTER ?= 0 +BORDER_ROUTING ?= 0 COAP ?= 0 COAP_OBSERVE ?= 0 COAPS ?= 0 @@ -97,6 +98,10 @@ ifeq ($(BORDER_ROUTER),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE=1 endif +ifeq ($(BORDER_ROUTING),1) +COMMONCFLAGS += -DOPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE=1 +endif + ifeq ($(COAP),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_COAP_API_ENABLE=1 endif @@ -319,6 +324,7 @@ LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_LEVEL=OT_LOG_LEVEL_DEBG LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_API=1 LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_ARP=1 LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_BBR=1 +LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_BR=1 LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_CLI=1 LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_COAP=1 LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_DUA=1 diff --git a/include/Makefile.am b/include/Makefile.am index e5554d3a9..57e582996 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -89,6 +89,7 @@ ot_platform_headers = \ openthread/platform/diag.h \ openthread/platform/flash.h \ openthread/platform/entropy.h \ + openthread/platform/infra_if.h \ openthread/platform/memory.h \ openthread/platform/misc.h \ openthread/platform/logging.h \ diff --git a/include/openthread/BUILD.gn b/include/openthread/BUILD.gn index 47b6b73f8..fa64a47c2 100644 --- a/include/openthread/BUILD.gn +++ b/include/openthread/BUILD.gn @@ -96,6 +96,7 @@ source_set("openthread") { "platform/diag.h", "platform/entropy.h", "platform/flash.h", + "platform/infra_if.h", "platform/logging.h", "platform/memory.h", "platform/messagepool.h", diff --git a/include/openthread/border_router.h b/include/openthread/border_router.h index e5e413dbc..841d8cd88 100644 --- a/include/openthread/border_router.h +++ b/include/openthread/border_router.h @@ -52,6 +52,19 @@ extern "C" { * */ +/** + * This method initializes the Border Routing Manager on given infrastructure interface. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aInfraIfIndex The infrastructure interface index. + * + * @retval OT_ERROR_NONE Successfully started the border routing manager on given infrastructure. + * @retval OT_ERROR_INVALID_ARGS The index of the infra interface is not valid. + * @retval OT_ERROR_FAILED Internal failure. This is usually failed to generate random prefixes. + * + */ +otError otBorderRoutingInit(otInstance *aInstance, uint32_t aInfraIfIndex); + /** * This method provides a full or stable copy of the local Thread Network Data. * diff --git a/include/openthread/icmp6.h b/include/openthread/icmp6.h index ca8c62481..5a02c7601 100644 --- a/include/openthread/icmp6.h +++ b/include/openthread/icmp6.h @@ -64,6 +64,8 @@ typedef enum otIcmp6Type OT_ICMP6_TYPE_PARAMETER_PROBLEM = 4, ///< Parameter Problem OT_ICMP6_TYPE_ECHO_REQUEST = 128, ///< Echo Request OT_ICMP6_TYPE_ECHO_REPLY = 129, ///< Echo Reply + OT_ICMP6_TYPE_ROUTER_SOLICIT = 133, ///< Router Solicitation + OT_ICMP6_TYPE_ROUTER_ADVERT = 134, ///< Router Advertisement } otIcmp6Type; /** diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 1e8575f47..700e4d710 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (58) +#define OPENTHREAD_API_VERSION (59) /** * @addtogroup api-instance diff --git a/include/openthread/platform/infra_if.h b/include/openthread/platform/infra_if.h new file mode 100644 index 000000000..ebca43b18 --- /dev/null +++ b/include/openthread/platform/infra_if.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * @brief + * This file includes the platform abstraction for the infrastructure network interface. + * + */ + +#ifndef OPENTHREAD_PLATFORM_INFRA_IF_H_ +#define OPENTHREAD_PLATFORM_INFRA_IF_H_ + +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This method sends an ICMPv6 Neighbor Discovery message on given infrastructure interface. + * + * See RFC 4861: https://tools.ietf.org/html/rfc4861. + * + * @param[in] aInfraIfIndex The index of the infrastructure interface this message is sent to. + * @param[in] aDestAddress The destination address this message is sent to. + * @param[in] aBuffer The ICMPv6 message buffer. + * @param[in] aBufferLength The length of the message buffer. + * + * @note Per RFC 4861, the implementation should send the message with IPv6 link-local source address + * of interface @p aInfraIfIndex and IP Hop Limit 255. + * + * @retval OT_ERROR_NONE Successfully sent the ICMPv6 message. + * @retval OT_ERROR_FAILED Failed to send the ICMPv6 message. + * + */ +otError otPlatInfraIfSendIcmp6Nd(uint32_t aInfraIfIndex, + const otIp6Address *aDestAddress, + const uint8_t * aBuffer, + uint16_t aBufferLength); + +/** + * The infra interface driver calls this method to notify OpenThread + * that an ICMPv6 Neighbor Discovery message is received. + * + * See RFC 4861: https://tools.ietf.org/html/rfc4861. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aInfraIfIndex The index of the infrastructure interface on which the ICMPv6 message is received. + * @param[in] aSrcAddress The source address this message is received from. + * @param[in] aBuffer The ICMPv6 message buffer. + * @param[in] aBufferLength The length of the ICMPv6 message buffer. + * + * @note Per RFC 4861, the caller should enforce that the source address MUST be a IPv6 link-local + * address and the IP Hop Limit MUST be 255. + * + * @note ICMPv6 message received from @p aInfraIfIndex via multicast loopback should not be passed in. + * + */ +extern void otPlatInfraIfRecvIcmp6Nd(otInstance * aInstance, + uint32_t aInfraIfIndex, + const otIp6Address *aSrcAddress, + const uint8_t * aBuffer, + uint16_t aBufferLength); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // OPENTHREAD_PLATFORM_INFRA_IF_H_ diff --git a/include/openthread/platform/logging.h b/include/openthread/platform/logging.h index 28676608f..0808ee911 100644 --- a/include/openthread/platform/logging.h +++ b/include/openthread/platform/logging.h @@ -137,6 +137,7 @@ typedef enum otLogRegion OT_LOG_REGION_BBR = 17, ///< Backbone Router (available since Thread 1.2) OT_LOG_REGION_MLR = 18, ///< Multicast Listener Registration (available since Thread 1.2) OT_LOG_REGION_DUA = 19, ///< Domain Unicast Address (available since Thread 1.2) + OT_LOG_REGION_BR = 20, ///< Border Router } otLogRegion; /** diff --git a/script/test b/script/test index 7e20f1305..7af8b543a 100755 --- a/script/test +++ b/script/test @@ -44,6 +44,7 @@ readonly OT_NATIVE_IP="${OT_NATIVE_IP:-0}" readonly THREAD_VERSION="${THREAD_VERSION:-1.1}" readonly INTER_OP="${INTER_OP:-0}" readonly VERBOSE="${VERBOSE:-0}" +readonly BORDER_ROUTING="${BORDER_ROUTING:-1}" build_simulation() { @@ -231,9 +232,13 @@ do_build_otbr_docker() echo "Building OTBR Docker ..." local otdir local otbrdir - local otbr_options="-DOT_DUA=ON -DOT_MLR=ON -DOT_COVERAGE=ON -DOTBR_REST=OFF -DOTBR_WEB=OFF" + local otbr_options="-DOT_SLAAC=ON -DOT_DUA=ON -DOT_MLR=ON -DOT_COVERAGE=ON -DOTBR_REST=OFF -DOTBR_WEB=OFF" local otbr_docker_image=${OTBR_DOCKER_IMAGE:-otbr-ot12-backbone-ci} + if [[ ${BORDER_ROUTING} == "1" ]]; then + otbr_options="${otbr_options} -DOT_BORDER_ROUTING=ON" + fi + otbrdir=$(mktemp -d -t otbr_XXXXXX) otdir=$(pwd) diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 7d0aa52b7..d3f800bc0 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -74,6 +74,10 @@ if (openthread_enable_core_config_args) { defines += [ "OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE=1" ] } + if (openthread_config_border_routing_enable) { + defines += [ "OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE=1" ] + } + if (openthread_external_mbedtls != "") { defines += [ "OPENTHREAD_CONFIG_ENABLE_BUILTIN_MBEDTLS=0" ] } else if (!openthread_config_enable_builtin_mbedtls_management) { @@ -234,6 +238,7 @@ if (openthread_enable_core_config_args) { "OPENTHREAD_CONFIG_LOG_API=1", "OPENTHREAD_CONFIG_LOG_ARP=1", "OPENTHREAD_CONFIG_LOG_BBR=1", + "OPENTHREAD_CONFIG_LOG_BR=1", "OPENTHREAD_CONFIG_LOG_CLI=1", "OPENTHREAD_CONFIG_LOG_COAP=1", "OPENTHREAD_CONFIG_LOG_DUA=1", @@ -342,6 +347,11 @@ openthread_core_files = [ "backbone_router/multicast_listeners_table.hpp", "backbone_router/ndproxy_table.cpp", "backbone_router/ndproxy_table.hpp", + "border_router/infra_if_platform.cpp", + "border_router/router_advertisement.cpp", + "border_router/router_advertisement.hpp", + "border_router/routing_manager.cpp", + "border_router/routing_manager.hpp", "coap/coap.cpp", "coap/coap.hpp", "coap/coap_message.cpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f1b945583..c8f0541d8 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -76,6 +76,9 @@ set(COMMON_SOURCES backbone_router/bbr_manager.cpp backbone_router/multicast_listeners_table.cpp backbone_router/ndproxy_table.cpp + border_router/infra_if_platform.cpp + border_router/router_advertisement.cpp + border_router/routing_manager.cpp coap/coap.cpp coap/coap_message.cpp coap/coap_secure.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 8daddd7e0..fa1958d30 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -153,6 +153,9 @@ SOURCES_COMMON = \ backbone_router/bbr_manager.cpp \ backbone_router/multicast_listeners_table.cpp \ backbone_router/ndproxy_table.cpp \ + border_router/infra_if_platform.cpp \ + border_router/router_advertisement.cpp \ + border_router/routing_manager.cpp \ coap/coap.cpp \ coap/coap_message.cpp \ coap/coap_secure.cpp \ @@ -346,6 +349,8 @@ HEADERS_COMMON = \ backbone_router/bbr_manager.hpp \ backbone_router/multicast_listeners_table.hpp \ backbone_router/ndproxy_table.hpp \ + border_router/router_advertisement.hpp \ + border_router/routing_manager.hpp \ coap/coap.hpp \ coap/coap_message.hpp \ coap/coap_secure.hpp \ diff --git a/src/core/api/border_router_api.cpp b/src/core/api/border_router_api.cpp index bf5b66fbb..3cb5e4be4 100644 --- a/src/core/api/border_router_api.cpp +++ b/src/core/api/border_router_api.cpp @@ -37,11 +37,21 @@ #include +#include "border_router/routing_manager.hpp" #include "common/debug.hpp" #include "common/instance.hpp" using namespace ot; +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +otError otBorderRoutingInit(otInstance *aInstance, uint32_t aInfraIfIndex) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().Init(aInfraIfIndex); +} +#endif + otError otBorderRouterGetNetData(otInstance *aInstance, bool aStable, uint8_t *aData, uint8_t *aDataLength) { Instance &instance = *static_cast(aInstance); diff --git a/src/core/border_router/infra_if_platform.cpp b/src/core/border_router/infra_if_platform.cpp new file mode 100644 index 000000000..720acca0c --- /dev/null +++ b/src/core/border_router/infra_if_platform.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements infrastructure interface platform APIs. + */ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + +#include + +#include "border_router/routing_manager.hpp" +#include "common/instance.hpp" + +using namespace ot; + +extern "C" void otPlatInfraIfRecvIcmp6Nd(otInstance * aInstance, + uint32_t aInfraIfIndex, + const otIp6Address *aSrcAddress, + const uint8_t * aBuffer, + uint16_t aBufferLength) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().RecvIcmp6Message( + aInfraIfIndex, static_cast(*aSrcAddress), aBuffer, aBufferLength); +} +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE diff --git a/src/core/border_router/router_advertisement.cpp b/src/core/border_router/router_advertisement.cpp new file mode 100644 index 000000000..c9cc1c24b --- /dev/null +++ b/src/core/border_router/router_advertisement.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes implementations for ICMPv6 Router Advertisement. + * + */ + +#include "border_router/router_advertisement.hpp" + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + +namespace ot { + +namespace BorderRouter { + +namespace RouterAdv { + +const Option *Option::GetNextOption(const Option *aCurOption, const uint8_t *aBuffer, uint16_t aBufferLength) +{ + const uint8_t *nextOption = nullptr; + const uint8_t *bufferEnd = aBuffer + aBufferLength; + + VerifyOrExit(aBuffer != nullptr, nextOption = nullptr); + + if (aCurOption == nullptr) + { + nextOption = aBuffer; + } + else + { + nextOption = reinterpret_cast(aCurOption) + aCurOption->GetLength(); + } + + VerifyOrExit(nextOption + sizeof(Option) <= bufferEnd, nextOption = nullptr); + VerifyOrExit(reinterpret_cast(nextOption)->GetLength() > 0, nextOption = nullptr); + VerifyOrExit(nextOption + reinterpret_cast(nextOption)->GetLength() <= bufferEnd, + nextOption = nullptr); + +exit: + return reinterpret_cast(nextOption); +} + +PrefixInfoOption::PrefixInfoOption(void) + : Option(Type::kPrefixInfo, sizeof(*this) / kLengthUnit) + , mPrefixLength(0) + , mReserved1(0) + , mValidLifetime(0) + , mPreferredLifetime(0) + , mReserved2(0) +{ + OT_UNUSED_VARIABLE(mReserved2); + + mPrefix.Clear(); +} + +void PrefixInfoOption::SetOnLink(bool aOnLink) +{ + if (aOnLink) + { + mReserved1 |= kOnLinkFlagMask; + } + else + { + mReserved1 &= ~kOnLinkFlagMask; + } +} + +void PrefixInfoOption::SetAutoAddrConfig(bool aAutoAddrConfig) +{ + if (aAutoAddrConfig) + { + mReserved1 |= kAutoConfigFlagMask; + } + else + { + mReserved1 &= ~kAutoConfigFlagMask; + } +} + +void PrefixInfoOption::SetPrefix(const Ip6::Prefix &aPrefix) +{ + mPrefixLength = aPrefix.mLength; + mPrefix = static_cast(aPrefix.mPrefix); +} + +void PrefixInfoOption::GetPrefix(Ip6::Prefix &aPrefix) const +{ + aPrefix.Set(mPrefix.GetBytes(), mPrefixLength); +} + +RouteInfoOption::RouteInfoOption(void) + : Option(Type::kRouteInfo, 0) + , mPrefixLength(0) + , mReserved(0) + , mRouteLifetime(0) +{ + OT_UNUSED_VARIABLE(mReserved); + + mPrefix.Clear(); +} + +void RouteInfoOption::SetPrefix(const Ip6::Prefix &aPrefix) +{ + // The total length (in bytes) of a Router Information Option + // is: (8 bytes fixed option header) + (0, 8, or 16 bytes prefix). + // Because the length of the option must be padded with 8 bytes, + // the length of the prefix (in bits) must be padded with 64 bits. + SetLength(((aPrefix.mLength + kLengthUnit * CHAR_BIT - 1) / (kLengthUnit * CHAR_BIT) + 1) * kLengthUnit); + + mPrefixLength = aPrefix.mLength; + mPrefix = static_cast(aPrefix.mPrefix); +} + +RouterAdvMessage::RouterAdvMessage(void) + : mReachableTime(0) + , mRetransTimer(0) +{ + OT_UNUSED_VARIABLE(mReachableTime); + OT_UNUSED_VARIABLE(mRetransTimer); + + mHeader.Clear(); + mHeader.SetType(Ip6::Icmp::Header::kTypeRouterAdvert); +} + +RouterSolicitMessage::RouterSolicitMessage(void) +{ + mHeader.Clear(); + mHeader.SetType(Ip6::Icmp::Header::kTypeRouterSolicit); +} + +} // namespace RouterAdv + +} // namespace BorderRouter + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE diff --git a/src/core/border_router/router_advertisement.hpp b/src/core/border_router/router_advertisement.hpp new file mode 100644 index 000000000..7411e5bc2 --- /dev/null +++ b/src/core/border_router/router_advertisement.hpp @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for IPv6 Router Advertisement. + * + * See RFC 4861: Neighbor Discovery for IP version 6 (https://tools.ietf.org/html/rfc4861). + * + */ + +#ifndef ROUTER_ADVERTISEMENT_HPP_ +#define ROUTER_ADVERTISEMENT_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + +#include + +#include + +#include "common/encoding.hpp" +#include "net/icmp6.hpp" +#include "net/ip6.hpp" + +using ot::Encoding::BigEndian::HostSwap16; +using ot::Encoding::BigEndian::HostSwap32; + +namespace ot { + +namespace BorderRouter { + +namespace RouterAdv { + +/** + * This class represents the variable length options in Neighbor + * Discovery messages. + * + * @sa PrefixInfoOption + * @sa RouteInfoOption + * + */ +OT_TOOL_PACKED_BEGIN +class Option +{ +public: + enum class Type : uint8_t + { + kPrefixInfo = 3, ///< Prefix Information Option. + kRouteInfo = 24, ///< Route Information Option. + }; + + enum : uint8_t + { + kLengthUnit = 8u, ///< The unit of length in octets. + }; + + /** + * This constructor initializes the option with given type and length. + * + * @param[in] aType The type of this option. + * @param[in] aLength The length of this option in unit of 8 octets. + * + */ + explicit Option(Type aType, uint8_t aLength = 0) + : mType(aType) + , mLength(aLength) + { + } + + /** + * This method returns the type of this option. + * + * @returns The option type. + * + */ + Type GetType(void) const { return mType; } + + /** + * This method sets the length of the option (in bytes). + * + * Since the option must end on their natural 64-bits boundaries, + * the actual length set to the option is padded to (aLength + 7) / 8 * 8. + * + * @param[in] aLength The length of the option in unit of 1 byte. + * + */ + void SetLength(uint16_t aLength) { mLength = (aLength + kLengthUnit - 1) / kLengthUnit; } + + /** + * This method returns the length of the option (in bytes). + * + * @returns The length of the option. + * + */ + uint16_t GetLength(void) const { return mLength * 8; } + + /** + * This helper method returns a pointer to the next valid option in the buffer. + * + * @param[in] aCurOption The current option. Use nullptr to get the first option. + * @param[in] aBuffer The buffer within which the options are held. + * @param[in] aBufferLength The length of the buffer. + * + * @returns A pointer to the next option if there are a valid one. Otherwise, nullptr. + * + */ + static const Option *GetNextOption(const Option *aCurOption, const uint8_t *aBuffer, uint16_t aBufferLength); + + /** + * This method tells whether this option is valid. + * + * @return A boolean that indicates whether this option is valid. + * + */ + bool IsValid(void) const { return mLength > 0; } + +private: + Type mType; // Type of the option. + uint8_t mLength; // Length of the option in unit of 8 octets, + // including the `type` and `length` fields. +} OT_TOOL_PACKED_END; + +/** + * This class represents the Prefix Information Option. + * + * See section 4.6.2 of RFC 4861 for definition of this option. + * https://tools.ietf.org/html/rfc4861#section-4.6.2 + * + */ +OT_TOOL_PACKED_BEGIN +class PrefixInfoOption : public Option +{ +public: + /** + * This constructor initializes this option with zero prefix + * length, valid lifetime and preferred lifetime. + * + */ + PrefixInfoOption(void); + + /** + * This method sets the on-link (L) flag. + * + * @param[in] aOnLink A boolean indicates whether the prefix is on-link or off-link. + * + */ + void SetOnLink(bool aOnLink); + + /** + * This method sets the autonomous address-configuration (A) flag. + * + * @param[in] aAutoAddrConfig A boolean indicates whether this prefix can be used + * for SLAAC. + * + */ + void SetAutoAddrConfig(bool aAutoAddrConfig); + + /** + * This method set the valid lifetime of the prefix in seconds. + * + * @param[in] aValidLifetime The valid lifetime in seconds. + * + */ + void SetValidLifetime(uint32_t aValidLifetime) { mValidLifetime = HostSwap32(aValidLifetime); } + + /** + * THis method returns the valid lifetime of the prefix in seconds. + * + * @returns The valid lifetime in seconds. + * + */ + uint32_t GetValidLifetime(void) const { return HostSwap32(mValidLifetime); } + + /** + * This method sets the preferred lifetime of the prefix in seconds. + * + * @param[in] aPreferredLifetime The preferred lifetime in seconds. + * + */ + void SetPreferredLifetime(uint32_t aPreferredLifetime) { mPreferredLifetime = HostSwap32(aPreferredLifetime); } + + /** + * This method sets the prefix. + * + * @param[in] aPrefix The prefix contained in this option. + * + */ + void SetPrefix(const Ip6::Prefix &aPrefix); + + /** + * THis method returns the prefix in this option. + * + * @param[out] aPrefix The prefix to output to. + * + */ + void GetPrefix(Ip6::Prefix &aPrefix) const; + + /** + * This method tells whether this option is valid. + * + * @returns A boolean indicates whether this option is valid. + * + */ + bool IsValid(void) const + { + return (GetLength() == sizeof(*this)) && (mPrefixLength <= OT_IP6_ADDRESS_SIZE * CHAR_BIT); + } + +private: + enum : uint8_t + { + kAutoConfigFlagMask = 0x40u, // Bit mask of the Automatic Address Configure flag. + kOnLinkFlagMask = 0x80u, // Bit mask of the On-link flag. + }; + + uint8_t mPrefixLength; // The prefix length in bits. + uint8_t mReserved1; // The reserved field. + uint32_t mValidLifetime; // The valid lifetime of the prefix. + uint32_t mPreferredLifetime; // The preferred lifetime of the prefix. + uint32_t mReserved2; // The reserved field. + Ip6::Address mPrefix; // The prefix. +} OT_TOOL_PACKED_END; + +static_assert(sizeof(PrefixInfoOption) == 32, "invalid PrefixInfoOption structure"); + +/** + * This class represents the Route Information Option. + * + * See section 2.3 of RFC 4191 for definition of this option. + * https://tools.ietf.org/html/rfc4191#section-2.3 + * + */ +OT_TOOL_PACKED_BEGIN +class RouteInfoOption : public Option +{ +public: + /** + * This constructor initializes this option with zero prefix length. + * + */ + RouteInfoOption(void); + + /** + * This method sets the lifetime of the route in seconds. + * + * @param[in] aLifetime The lifetime of the route in seconds. + * + */ + void SetRouteLifetime(uint32_t aLifetime) { mRouteLifetime = HostSwap32(aLifetime); } + + /** + * This method sets the prefix. + * + * @param[in] aPrefix The prefix contained in this option. + * + */ + void SetPrefix(const Ip6::Prefix &aPrefix); + + /** + * This method tells whether this option is valid. + * + * @returns A boolean indicates whether this option is valid. + * + */ + bool IsValid(void) const + { + return (GetLength() == kLengthUnit || GetLength() == 2 * kLengthUnit || GetLength() == 3 * kLengthUnit) && + (mPrefixLength <= OT_IP6_ADDRESS_SIZE * CHAR_BIT); + } + +private: + uint8_t mPrefixLength; // The prefix length in bits. + uint8_t mReserved; // The reserved field. + uint32_t mRouteLifetime; // The lifetime in seconds. + Ip6::Address mPrefix; // The prefix. +} OT_TOOL_PACKED_END; + +static_assert(sizeof(RouteInfoOption) == 24, "invalid RouteInfoOption structure"); + +/** + * This class implements the Router Advertisement message. + * + * See section 4.2 of RFC 4861 for definition of this message. + * https://tools.ietf.org/html/rfc4861#section-4.2 + * + */ +OT_TOOL_PACKED_BEGIN +class RouterAdvMessage +{ +public: + /** + * This constructor initializes the Router Advertisement message with + * zero router lifetime, reachable time and retransmission timer. + * + */ + RouterAdvMessage(void); + + /** + * This method sets the Router Lifetime in seconds. + * + * Zero Router Lifetime means we are not a default router. + * + * @param[in] aRouterLifetime The router lifetime in seconds. + * + */ + void SetRouterLifetime(uint16_t aRouterLifetime) + { + mHeader.mData.m16[kRouteLifetimeIdx] = HostSwap16(aRouterLifetime); + } + +private: + enum : uint8_t + { + kRouteLifetimeIdx = 1u, // The index of Route Lifetime in ICMPv6 Header Data. in unit of 2 octets. + }; + + Ip6::Icmp::Header mHeader; // The common ICMPv6 header. + uint32_t mReachableTime; // The reachable time. In milliseconds. + uint32_t mRetransTimer; // The retransmission timer. In milliseconds. +} OT_TOOL_PACKED_END; + +static_assert(sizeof(RouterAdvMessage) == 16, "invalid RouterAdvMessage structure"); + +/** + * This class implements the Router Solicitation message. + * + * See section 4.1 of RFC 4861 for definition of this message. + * https://tools.ietf.org/html/rfc4861#section-4.1 + * + */ +OT_TOOL_PACKED_BEGIN +class RouterSolicitMessage +{ +public: + /** + * This constructor initializes the Router Solicitation message. + * + */ + RouterSolicitMessage(void); + +private: + Ip6::Icmp::Header mHeader; // The common ICMPv6 header. +} OT_TOOL_PACKED_END; + +static_assert(sizeof(RouterSolicitMessage) == 8, "invalid RouterSolicitMessage structure"); + +} // namespace RouterAdv + +} // namespace BorderRouter + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + +#endif // ROUTER_ADVERTISEMENT_HPP_ diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp new file mode 100644 index 000000000..e6b501347 --- /dev/null +++ b/src/core/border_router/routing_manager.cpp @@ -0,0 +1,795 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes implementation for the RA-based routing management. + * + */ + +#include "border_router/routing_manager.hpp" + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + +#include + +#include + +#include "common/code_utils.hpp" +#include "common/debug.hpp" +#include "common/instance.hpp" +#include "common/locator-getters.hpp" +#include "common/logging.hpp" +#include "common/random.hpp" +#include "common/settings.hpp" +#include "net/ip6.hpp" +#include "thread/network_data.hpp" +#include "thread/network_data_leader.hpp" +#include "thread/network_data_local.hpp" +#include "thread/network_data_notifier.hpp" + +namespace ot { + +namespace BorderRouter { + +RoutingManager::RoutingManager(Instance &aInstance) + : InstanceLocator(aInstance) + , mIsRunning(false) + , mInfraIfIndex(0) + , mAdvertisedOmrPrefixNum(0) + , mAdvertisedOnLinkPrefix(nullptr) + , mRouterAdvertisementTimer(aInstance, HandleRouterAdvertisementTimer, this) + , mRouterAdvertisementCount(0) + , mRouterSolicitTimer(aInstance, HandleRouterSolicitTimer, this) + , mRouterSolicitCount(0) + , mDiscoveredOnLinkPrefixInvalidTimer(aInstance, HandleDiscoveredOnLinkPrefixInvalidTimer, this) +{ + mLocalOmrPrefix.Clear(); + memset(mAdvertisedOmrPrefixes, 0, sizeof(mAdvertisedOmrPrefixes)); + + mLocalOnLinkPrefix.Clear(); + mDiscoveredOnLinkPrefix.Clear(); +} + +otError RoutingManager::Init(uint32_t aInfraIfIndex) +{ + otError error; + + OT_ASSERT(!IsInitialized() && !Get().IsAttached()); + VerifyOrExit(aInfraIfIndex > 0, error = OT_ERROR_INVALID_ARGS); + + SuccessOrExit(error = LoadOrGenerateRandomOmrPrefix()); + SuccessOrExit(error = LoadOrGenerateRandomOnLinkPrefix()); + + mInfraIfIndex = aInfraIfIndex; + +exit: + return error; +} + +otError RoutingManager::LoadOrGenerateRandomOmrPrefix(void) +{ + otError error = OT_ERROR_NONE; + + if (Get().ReadOmrPrefix(mLocalOmrPrefix) != OT_ERROR_NONE || !IsValidOmrPrefix(mLocalOmrPrefix)) + { + Ip6::NetworkPrefix randomOmrPrefix; + + otLogNoteBr("no valid OMR prefix found in settings, generating new one"); + + error = randomOmrPrefix.GenerateRandomUla(); + if (error != OT_ERROR_NONE) + { + otLogCritBr("failed to generate random OMR prefix"); + ExitNow(); + } + + mLocalOmrPrefix.Set(randomOmrPrefix); + IgnoreError(Get().SaveOmrPrefix(mLocalOmrPrefix)); + } + +exit: + return error; +} + +otError RoutingManager::LoadOrGenerateRandomOnLinkPrefix(void) +{ + otError error = OT_ERROR_NONE; + + if (Get().ReadOnLinkPrefix(mLocalOnLinkPrefix) != OT_ERROR_NONE || + !IsValidOnLinkPrefix(mLocalOnLinkPrefix)) + { + Ip6::NetworkPrefix randomOnLinkPrefix; + + otLogNoteBr("no valid on-link prefix found in settings, generating new one"); + + error = randomOnLinkPrefix.GenerateRandomUla(); + if (error != OT_ERROR_NONE) + { + otLogCritBr("failed to generate random on-link prefix"); + ExitNow(); + } + + randomOnLinkPrefix.m8[6] = 0; + randomOnLinkPrefix.m8[7] = 0; + mLocalOnLinkPrefix.Set(randomOnLinkPrefix); + + IgnoreError(Get().SaveOnLinkPrefix(mLocalOnLinkPrefix)); + } + +exit: + return error; +} + +void RoutingManager::Start(void) +{ + if (!mIsRunning) + { + mIsRunning = true; + StartRouterSolicitation(); + } +} + +void RoutingManager::Stop(void) +{ + VerifyOrExit(mIsRunning); + + UnpublishLocalOmrPrefix(); + + // Use empty OMR & on-link prefixes to invalidate possible advertised prefixes. + SendRouterAdvertisement(nullptr, 0, nullptr); + + memset(mAdvertisedOmrPrefixes, 0, sizeof(mAdvertisedOmrPrefixes)); + mAdvertisedOmrPrefixNum = 0; + + mAdvertisedOnLinkPrefix = nullptr; + + mDiscoveredOnLinkPrefix.Clear(); + mDiscoveredOnLinkPrefixInvalidTimer.Stop(); + + mRouterAdvertisementTimer.Stop(); + mRouterAdvertisementCount = 0; + + mRouterSolicitTimer.Stop(); + mRouterSolicitCount = 0; + + mIsRunning = false; + +exit: + return; +} + +void RoutingManager::RecvIcmp6Message(uint32_t aInfraIfIndex, + const Ip6::Address &aSrcAddress, + const uint8_t * aBuffer, + uint16_t aBufferLength) +{ + const Ip6::Icmp::Header *icmp6Header; + + VerifyOrExit(mIsRunning); + + VerifyOrExit(aInfraIfIndex == mInfraIfIndex); + VerifyOrExit(aBuffer != nullptr && aBufferLength >= sizeof(*icmp6Header)); + + icmp6Header = reinterpret_cast(aBuffer); + + switch (icmp6Header->GetType()) + { + case Ip6::Icmp::Header::kTypeRouterAdvert: + HandleRouterAdvertisement(aSrcAddress, aBuffer, aBufferLength); + break; + case Ip6::Icmp::Header::kTypeRouterSolicit: + HandleRouterSolicit(aSrcAddress, aBuffer, aBufferLength); + break; + default: + break; + } + +exit: + return; +} + +void RoutingManager::HandleNotifierEvents(Events aEvents) +{ + VerifyOrExit(IsInitialized()); + + if (aEvents.Contains(kEventThreadRoleChanged)) + { + if (Get().IsAttached()) + { + Start(); + } + else + { + Stop(); + } + } + + if (aEvents.Contains(kEventThreadNetdataChanged)) + { + EvaluateRoutingPolicy(); + } + +exit: + return; +} + +uint8_t RoutingManager::EvaluateOmrPrefix(Ip6::Prefix *aNewOmrPrefixes, uint8_t aMaxOmrPrefixNum) +{ + uint8_t newOmrPrefixNum = 0; + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::OnMeshPrefixConfig onMeshPrefixConfig; + Ip6::Prefix * smallestOmrPrefix = nullptr; + Ip6::Prefix * publishedLocalOmrPrefix = nullptr; + + OT_ASSERT(mIsRunning); + + while (Get().GetNextOnMeshPrefix(iterator, onMeshPrefixConfig) == OT_ERROR_NONE) + { + uint8_t newPrefixIndex; + + if (!IsValidOmrPrefix(onMeshPrefixConfig.GetPrefix()) || !onMeshPrefixConfig.mDefaultRoute || + !onMeshPrefixConfig.mSlaac || onMeshPrefixConfig.mDp) + { + continue; + } + + newPrefixIndex = 0; + while (newPrefixIndex < newOmrPrefixNum && onMeshPrefixConfig.GetPrefix() != aNewOmrPrefixes[newPrefixIndex]) + { + ++newPrefixIndex; + } + + if (newPrefixIndex != newOmrPrefixNum) + { + // Ignore duplicate prefixes. + continue; + } + + if (newOmrPrefixNum >= aMaxOmrPrefixNum) + { + otLogWarnBr("EvaluateOmrPrefix: too many OMR prefixes, ignoring prefix %s", + onMeshPrefixConfig.GetPrefix().ToString().AsCString()); + continue; + } + + aNewOmrPrefixes[newOmrPrefixNum] = onMeshPrefixConfig.GetPrefix(); + if (smallestOmrPrefix == nullptr || IsPrefixSmallerThan(onMeshPrefixConfig.GetPrefix(), *smallestOmrPrefix)) + { + smallestOmrPrefix = &aNewOmrPrefixes[newOmrPrefixNum]; + } + if (aNewOmrPrefixes[newOmrPrefixNum] == mLocalOmrPrefix) + { + publishedLocalOmrPrefix = &aNewOmrPrefixes[newOmrPrefixNum]; + } + ++newOmrPrefixNum; + } + + // Decide if we need to add or remove my local OMR prefix. + if (newOmrPrefixNum == 0) + { + otLogInfoBr("EvaluateOmrPrefix: no valid OMR prefixes found in Thread network"); + if (PublishLocalOmrPrefix() == OT_ERROR_NONE) + { + aNewOmrPrefixes[newOmrPrefixNum++] = mLocalOmrPrefix; + } + + // The `newOmrPrefixNum` is zero when we failed to publish the local OMR prefix. + } + else if (publishedLocalOmrPrefix != nullptr && smallestOmrPrefix != publishedLocalOmrPrefix) + { + otLogInfoBr("EvaluateOmrPrefix: there is already a smaller OMR prefix %s in the Thread network", + smallestOmrPrefix->ToString().AsCString()); + UnpublishLocalOmrPrefix(); + + // Remove the local OMR prefix from the list by overwriting it with the last one. + *publishedLocalOmrPrefix = aNewOmrPrefixes[--newOmrPrefixNum]; + } + + return newOmrPrefixNum; +} + +otError RoutingManager::PublishLocalOmrPrefix(void) +{ + otError error = OT_ERROR_NONE; + NetworkData::OnMeshPrefixConfig omrPrefixConfig; + + OT_ASSERT(mIsRunning); + + omrPrefixConfig.Clear(); + omrPrefixConfig.mPrefix = mLocalOmrPrefix; + omrPrefixConfig.mStable = true; + omrPrefixConfig.mSlaac = true; + omrPrefixConfig.mPreferred = true; + omrPrefixConfig.mOnMesh = true; + omrPrefixConfig.mDefaultRoute = true; + + error = Get().AddOnMeshPrefix(omrPrefixConfig); + if (error != OT_ERROR_NONE) + { + otLogInfoBr("failed to publish local OMR prefix %s in Thread network: %s", + mLocalOmrPrefix.ToString().AsCString(), otThreadErrorToString(error)); + } + else + { + Get().HandleServerDataUpdated(); + otLogInfoBr("published local OMR prefix %s in Thread network", mLocalOmrPrefix.ToString().AsCString()); + } + + return error; +} + +void RoutingManager::UnpublishLocalOmrPrefix(void) +{ + VerifyOrExit(mIsRunning); + + IgnoreError(Get().RemoveOnMeshPrefix(mLocalOmrPrefix)); + Get().HandleServerDataUpdated(); + + otLogInfoBr("unpubished local OMR prefix %s from Thread network", mLocalOmrPrefix.ToString().AsCString()); + +exit: + return; +} + +bool RoutingManager::ContainsPrefix(const Ip6::Prefix &aPrefix, const Ip6::Prefix *aPrefixList, uint8_t aPrefixNum) +{ + bool ret = false; + + for (uint8_t i = 0; i < aPrefixNum; ++i) + { + if (aPrefixList[i] == aPrefix) + { + ret = true; + break; + } + } + + return ret; +} + +const Ip6::Prefix *RoutingManager::EvaluateOnLinkPrefix(void) const +{ + const Ip6::Prefix *newOnLinkPrefix = nullptr; + + // We don't evaluate on-link prefix if we are doing + // Router Discovery or we have already discovered some + // on-link prefixes. + VerifyOrExit(!mRouterSolicitTimer.IsRunning()); + if (IsValidOnLinkPrefix(mDiscoveredOnLinkPrefix)) + { + otLogInfoBr("EvaluateOnLinkPrefix: there is already on-link prefix %s on interface %u", + mDiscoveredOnLinkPrefix.ToString().AsCString(), mInfraIfIndex); + ExitNow(); + } + + newOnLinkPrefix = &mLocalOnLinkPrefix; + +exit: + return newOnLinkPrefix; +} + +void RoutingManager::EvaluateRoutingPolicy(void) +{ + const Ip6::Prefix *newOnLinkPrefix = nullptr; + Ip6::Prefix newOmrPrefixes[kMaxOmrPrefixNum]; + uint8_t newOmrPrefixNum = 0; + + VerifyOrExit(mIsRunning); + + otLogInfoBr("evaluating routing policy"); + + // 0. Evaluate on-link & OMR prefixes. + newOnLinkPrefix = EvaluateOnLinkPrefix(); + newOmrPrefixNum = EvaluateOmrPrefix(newOmrPrefixes, kMaxOmrPrefixNum); + + // 1. Send Router Advertisement message if necessary. + SendRouterAdvertisement(newOmrPrefixes, newOmrPrefixNum, newOnLinkPrefix); + if (newOmrPrefixNum == 0) + { + // This is the very exceptional case and happens only when we failed to publish + // our local OMR prefix to the Thread network. We schedule the Router Advertisement + // timer to re-evaluate our routing policy in the future. + otLogWarnBr("no OMR prefix advertised! Start Router Advertisement timer for future evaluation"); + } + + // 2. Schedule Router Advertisement timer with random interval. + { + uint32_t nextSendTime; + + nextSendTime = Random::NonCrypto::GetUint32InRange(kMinRtrAdvInterval, kMaxRtrAdvInterval); + + if (mRouterAdvertisementCount <= kMaxInitRtrAdvertisements && nextSendTime > kMaxInitRtrAdvInterval) + { + nextSendTime = kMaxInitRtrAdvInterval; + } + + otLogInfoBr("router advertisement scheduled in %u seconds", nextSendTime); + mRouterAdvertisementTimer.Start(nextSendTime * 1000); + } + + // 3. Update advertised on-link & OMR prefixes information. + mAdvertisedOnLinkPrefix = newOnLinkPrefix; + static_assert(sizeof(mAdvertisedOmrPrefixes) == sizeof(newOmrPrefixes), "invalid new OMR prefix array size"); + memcpy(mAdvertisedOmrPrefixes, newOmrPrefixes, sizeof(newOmrPrefixes[0]) * newOmrPrefixNum); + mAdvertisedOmrPrefixNum = newOmrPrefixNum; + +exit: + return; +} + +void RoutingManager::StartRouterSolicitation(void) +{ + mRouterSolicitCount = 0; + mRouterSolicitTimer.Start(Random::NonCrypto::GetUint32InRange(0, kMaxRtrSolicitationDelay * 1000)); +} + +otError RoutingManager::SendRouterSolicitation(void) +{ + Ip6::Address destAddress; + RouterAdv::RouterSolicitMessage routerSolicit; + + OT_ASSERT(IsInitialized()); + + destAddress.SetToLinkLocalAllRoutersMulticast(); + return otPlatInfraIfSendIcmp6Nd(mInfraIfIndex, &destAddress, reinterpret_cast(&routerSolicit), + sizeof(routerSolicit)); +} + +void RoutingManager::SendRouterAdvertisement(const Ip6::Prefix *aNewOmrPrefixes, + uint8_t aNewOmrPrefixNum, + const Ip6::Prefix *aNewOnLinkPrefix) +{ + uint8_t buffer[kMaxRouterAdvMessageLength]; + uint16_t bufferLength = 0; + RouterAdv::RouterAdvMessage routerAdv; + + // Set zero Router Lifetime to indicate that the Border Router is not the default + // router for infra link so that hosts on infra link will not create default route + // to the Border Router when received RA. + routerAdv.SetRouterLifetime(0); + + OT_ASSERT(bufferLength + sizeof(routerAdv) <= sizeof(buffer)); + memcpy(buffer, &routerAdv, sizeof(routerAdv)); + bufferLength += sizeof(routerAdv); + + if (aNewOnLinkPrefix != nullptr) + { + RouterAdv::PrefixInfoOption pio; + + pio.SetOnLink(true); + pio.SetAutoAddrConfig(true); + pio.SetValidLifetime(kDefaultOnLinkPrefixLifetime); + pio.SetPreferredLifetime(kDefaultOnLinkPrefixLifetime); + pio.SetPrefix(*aNewOnLinkPrefix); + + OT_ASSERT(bufferLength + pio.GetLength() <= sizeof(buffer)); + memcpy(buffer + bufferLength, &pio, pio.GetLength()); + bufferLength += pio.GetLength(); + + if (mAdvertisedOnLinkPrefix != nullptr) + { + otLogInfoBr("start advertising new on-link prefix %s on interface %u", + aNewOnLinkPrefix->ToString().AsCString(), mInfraIfIndex); + } + + otLogInfoBr("send on-link prefix %s in PIO (valid lifetime = %u seconds)", + aNewOnLinkPrefix->ToString().AsCString(), kDefaultOnLinkPrefixLifetime); + } + else if (mAdvertisedOnLinkPrefix != nullptr) + { + RouterAdv::PrefixInfoOption pio; + + // Set zero valid lifetime to immediately invalidate the advertised on-link prefix. + pio.SetValidLifetime(0); + pio.SetPreferredLifetime(0); + pio.SetPrefix(*mAdvertisedOnLinkPrefix); + + OT_ASSERT(bufferLength + pio.GetLength() <= sizeof(buffer)); + memcpy(buffer + bufferLength, &pio, pio.GetLength()); + bufferLength += pio.GetLength(); + + otLogInfoBr("stop advertising on-link prefix %s on interface %u", + mAdvertisedOnLinkPrefix->ToString().AsCString(), mInfraIfIndex); + } + + // Invalidate the advertised OMR prefixes if they are no longer in the new OMR prefix array. + for (uint8_t i = 0; i < mAdvertisedOmrPrefixNum; ++i) + { + const Ip6::Prefix &advertisedOmrPrefix = mAdvertisedOmrPrefixes[i]; + + if (!ContainsPrefix(advertisedOmrPrefix, aNewOmrPrefixes, aNewOmrPrefixNum)) + { + RouterAdv::RouteInfoOption rio; + + // Set zero route lifetime to immediately invalidate the advertised OMR prefix. + rio.SetRouteLifetime(0); + rio.SetPrefix(advertisedOmrPrefix); + + OT_ASSERT(bufferLength + rio.GetLength() <= sizeof(buffer)); + memcpy(buffer + bufferLength, &rio, rio.GetLength()); + bufferLength += rio.GetLength(); + + otLogInfoBr("stop advertising OMR prefix %s on interface %u", advertisedOmrPrefix.ToString().AsCString(), + mInfraIfIndex); + } + } + + for (uint8_t i = 0; i < aNewOmrPrefixNum; ++i) + { + const Ip6::Prefix & newOmrPrefix = aNewOmrPrefixes[i]; + RouterAdv::RouteInfoOption rio; + + rio.SetRouteLifetime(kDefaultOmrPrefixLifetime); + rio.SetPrefix(newOmrPrefix); + + OT_ASSERT(bufferLength + rio.GetLength() <= sizeof(buffer)); + memcpy(buffer + bufferLength, &rio, rio.GetLength()); + bufferLength += rio.GetLength(); + + otLogInfoBr("send OMR prefix %s in RIO (valid lifetime = %u seconds)", newOmrPrefix.ToString().AsCString(), + kDefaultOmrPrefixLifetime); + } + + // Send the message only when there are options. + if (bufferLength > sizeof(routerAdv)) + { + otError error; + Ip6::Address destAddress; + + ++mRouterAdvertisementCount; + + destAddress.SetToLinkLocalAllNodesMulticast(); + error = otPlatInfraIfSendIcmp6Nd(mInfraIfIndex, &destAddress, buffer, bufferLength); + + if (error == OT_ERROR_NONE) + { + otLogInfoBr("sent Router Advertisement on interface %u", mInfraIfIndex); + } + else + { + otLogWarnBr("failed to send Router Advertisement on interface %u: %s", mInfraIfIndex, + otThreadErrorToString(error)); + } + } +} + +bool RoutingManager::IsPrefixSmallerThan(const Ip6::Prefix &aFirstPrefix, const Ip6::Prefix &aSecondPrefix) +{ + uint8_t matchedLength; + + OT_ASSERT(aFirstPrefix.GetLength() == aSecondPrefix.GetLength()); + + matchedLength = + Ip6::Prefix::MatchLength(aFirstPrefix.GetBytes(), aSecondPrefix.GetBytes(), aFirstPrefix.GetBytesSize()); + + return matchedLength < aFirstPrefix.GetLength() && + aFirstPrefix.GetBytes()[matchedLength / CHAR_BIT] < aSecondPrefix.GetBytes()[matchedLength / CHAR_BIT]; +} + +bool RoutingManager::IsValidOmrPrefix(const Ip6::Prefix &aOmrPrefix) +{ + // Accept ULA prefix with length of 64 bits and GUA prefix. + return (aOmrPrefix.mLength == kOmrPrefixLength && aOmrPrefix.mPrefix.mFields.m8[0] == 0xfd) || + (aOmrPrefix.mLength >= 3 && (aOmrPrefix.GetBytes()[0] & 0xE0) == 0x20); +} + +bool RoutingManager::IsValidOnLinkPrefix(const Ip6::Prefix &aOnLinkPrefix) +{ + // Accept ULA prefix with length of 64 bits and GUA prefix. + return (aOnLinkPrefix.mLength == kOnLinkPrefixLength && aOnLinkPrefix.mPrefix.mFields.m8[0] == 0xfd) || + (aOnLinkPrefix.mLength >= 3 && (aOnLinkPrefix.GetBytes()[0] & 0xE0) == 0x20); +} + +void RoutingManager::HandleRouterAdvertisementTimer(Timer &aTimer) +{ + aTimer.GetOwner().HandleRouterAdvertisementTimer(); +} + +void RoutingManager::HandleRouterAdvertisementTimer(void) +{ + otLogInfoBr("router advertisement timer triggered"); + + EvaluateRoutingPolicy(); +} + +void RoutingManager::HandleRouterSolicitTimer(Timer &aTimer) +{ + aTimer.GetOwner().HandleRouterSolicitTimer(); +} + +void RoutingManager::HandleRouterSolicitTimer(void) +{ + otLogInfoBr("router solicitation times out"); + + if (mRouterSolicitCount < kMaxRtrSolicitations) + { + uint32_t nextSolicitationDelay; + otError error; + + error = SendRouterSolicitation(); + ++mRouterSolicitCount; + + if (error == OT_ERROR_NONE) + { + otLogDebgBr("successfully sent %uth Router Solicitation", mRouterSolicitCount); + } + else + { + otLogCritBr("failed to send %uth Router Solicitation: %s", mRouterSolicitCount, + otThreadErrorToString(error)); + } + + nextSolicitationDelay = + (mRouterSolicitCount == kMaxRtrSolicitations) ? kMaxRtrSolicitationDelay : kRtrSolicitationInterval; + + otLogDebgBr("router solicitation timer scheduled in %u seconds", nextSolicitationDelay); + mRouterSolicitTimer.Start(nextSolicitationDelay * 1000); + } + else + { + // Re-evaluate our routing policy and send Router Advertisement if necessary. + EvaluateRoutingPolicy(); + } +} + +void RoutingManager::HandleDiscoveredOnLinkPrefixInvalidTimer(Timer &aTimer) +{ + aTimer.GetOwner().HandleDiscoveredOnLinkPrefixInvalidTimer(); +} + +void RoutingManager::HandleDiscoveredOnLinkPrefixInvalidTimer(void) +{ + otLogInfoBr("invalidate discovered on-link prefix: %s", mDiscoveredOnLinkPrefix.ToString().AsCString()); + mDiscoveredOnLinkPrefix.Clear(); + + // The discovered on-link prefix becomes invalid, start Router Solicitation + // to discover new one. + StartRouterSolicitation(); +} + +void RoutingManager::HandleRouterSolicit(const Ip6::Address &aSrcAddress, + const uint8_t * aBuffer, + uint16_t aBufferLength) +{ + OT_UNUSED_VARIABLE(aSrcAddress); + OT_UNUSED_VARIABLE(aBuffer); + OT_UNUSED_VARIABLE(aBufferLength); + + otLogInfoBr("received Router Solicitation from %s on interface %u", aSrcAddress.ToString().AsCString(), + mInfraIfIndex); + + mRouterAdvertisementTimer.Start(Random::NonCrypto::GetUint32InRange(0, kMaxRaDelayTime)); +} + +uint32_t RoutingManager::GetPrefixExpireDelay(uint32_t aValidLifetime) +{ + uint32_t delay; + + if (aValidLifetime * static_cast(1000) > Timer::kMaxDelay) + { + delay = Timer::kMaxDelay; + } + else + { + delay = aValidLifetime * 1000; + } + + return delay; +} + +void RoutingManager::HandleRouterAdvertisement(const Ip6::Address &aSrcAddress, + const uint8_t * aBuffer, + uint16_t aBufferLength) +{ + OT_UNUSED_VARIABLE(aSrcAddress); + + using RouterAdv::Option; + using RouterAdv::PrefixInfoOption; + using RouterAdv::RouterAdvMessage; + + bool needReevaluate = false; + const uint8_t *optionsBegin; + uint16_t optionsLength; + const Option * option; + + VerifyOrExit(aBufferLength >= sizeof(RouterAdvMessage)); + + otLogInfoBr("received Router Advertisement from %s on interface %u", aSrcAddress.ToString().AsCString(), + mInfraIfIndex); + + optionsBegin = aBuffer + sizeof(RouterAdvMessage); + optionsLength = aBufferLength - sizeof(RouterAdvMessage); + + option = nullptr; + while ((option = Option::GetNextOption(option, optionsBegin, optionsLength)) != nullptr) + { + const PrefixInfoOption *pio; + Ip6::Prefix prefix; + + if (option->GetType() != Option::Type::kPrefixInfo) + { + continue; + } + + pio = static_cast(option); + + if (!pio->IsValid()) + { + continue; + } + + pio->GetPrefix(prefix); + if (!IsValidOnLinkPrefix(prefix)) + { + otLogInfoBr("ignore invalid prefix in PIO: %s", prefix.ToString().AsCString()); + continue; + } + + if (pio->GetValidLifetime() == 0) + { + if (mDiscoveredOnLinkPrefix == prefix) + { + otLogInfoBr("invalidate discovered on-link prefix %s", prefix.ToString().AsCString()); + mDiscoveredOnLinkPrefixInvalidTimer.Stop(); + mDiscoveredOnLinkPrefix.Clear(); + needReevaluate = true; + } + } + else + { + otLogInfoBr("set discovered on-link prefix to %s, valid lifetime: %u seconds", + prefix.ToString().AsCString(), pio->GetValidLifetime()); + + // We keep tracking the latest on-link prefix. + mDiscoveredOnLinkPrefixInvalidTimer.Start(GetPrefixExpireDelay(pio->GetValidLifetime())); + mDiscoveredOnLinkPrefix = prefix; + + // Stop Router Solicitation if we found a valid on-link prefix. + // Otherwise, we wait till the Router Solicitation process times out. + // So the maximum delay before the Border Router starts advertising + // its own on-link prefix is 9 (4 + 4 + 1) seconds. + mRouterSolicitTimer.Stop(); + needReevaluate = true; + } + } + + if (needReevaluate) + { + EvaluateRoutingPolicy(); + } + +exit: + return; +} + +} // namespace BorderRouter + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp new file mode 100644 index 000000000..45eb4d2e4 --- /dev/null +++ b/src/core/border_router/routing_manager.hpp @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for the RA-based routing management. + * + */ + +#ifndef ROUTING_MANAGER_HPP_ +#define ROUTING_MANAGER_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + +#include +#include + +#include "border_router/router_advertisement.hpp" +#include "common/locator.hpp" +#include "common/notifier.hpp" +#include "common/timer.hpp" +#include "net/ip6.hpp" + +namespace ot { + +namespace BorderRouter { + +/** + * This class implements bi-directional routing between Thread and + * Infrastructure networks. + * + * The routing manager works on both Thread interface and infrastructure + * interface. All ICMPv6 messages are sent/recv on the infrastructure + * interface. + * + */ +class RoutingManager : public InstanceLocator +{ + friend class ot::Notifier; + +public: + /** + * This constructor initializes the routing manager. + * + * @param[in] aInstance A OpenThread instance. + * + */ + explicit RoutingManager(Instance &aInstance); + + /** + * This method initializes the routing manager on given infrastructure interface. + * + * @param[in] aInfraIfIndex An infrastructure network interface index. + * + * @retval OT_ERROR_NONE Successfully started the routing manager. + * @retval OT_ERROR_INVALID_ARGS The index of the infra interface is not valid. + * + */ + otError Init(uint32_t aInfraIfIndex); + + /** + * This method receives an ICMPv6 message on the infrastructure interface. + * + * Malformed or undesired messages are dropped silently. + * + * @param[in] aInfraIfIndex The infrastructure interface index. + * @param[in] aSrcAddress The source address this message is sent from. + * @param[in] aBuffer THe ICMPv6 message buffer. + * @param[in] aLength The length of the ICMPv6 message buffer. + * + */ + void RecvIcmp6Message(uint32_t aInfraIfIndex, + const Ip6::Address &aSrcAddress, + const uint8_t * aBuffer, + uint16_t aBufferLength); + +private: + enum : uint16_t + { + kMaxRouterAdvMessageLength = 256u, // The maximum RA message length we can handle. + }; + + enum : uint8_t + { + kMaxOmrPrefixNum = + OPENTHREAD_CONFIG_IP6_SLAAC_NUM_ADDRESSES, // The maximum number of the OMR prefixes to advertise. + kOmrPrefixLength = OT_IP6_PREFIX_BITSIZE, // The length of an OMR prefix. In bits. + kOnLinkPrefixLength = OT_IP6_PREFIX_BITSIZE, // The length of an On-link prefix. In bits. + }; + + enum : uint32_t + { + kDefaultOmrPrefixLifetime = 1800u, // The default OMR prefix valid lifetime. In seconds. + kDefaultOnLinkPrefixLifetime = 1800u, // The default on-link prefix valid lifetime. In seconds. + kMaxRtrAdvInterval = 600, // Maximum Router Advertisement Interval. In seconds. + kMinRtrAdvInterval = kMaxRtrAdvInterval / 3, // Minimum Router Advertisement Interval. In seconds. + kMaxInitRtrAdvInterval = 16, // Maximum Initial Router Advertisement Interval. In seconds. + kMaxRaDelayTime = 500, // The maximum delay of sending RA after receiving RS. In milliseconds. + kRtrSolicitationInterval = 4, // The interval between Router Solicitations. In seconds. + kMaxRtrSolicitationDelay = 1, // The maximum delay for initial solicitation. In seconds. + }; + + static_assert(kMinRtrAdvInterval <= 3 * kMaxRtrAdvInterval / 4, "invalid RA intervals"); + static_assert(kDefaultOmrPrefixLifetime >= kMaxRtrAdvInterval, "invalid default OMR prefix lifetime"); + static_assert(kDefaultOnLinkPrefixLifetime >= kMaxRtrAdvInterval, "invalid default on-link prefix lifetime"); + + enum : uint32_t + { + kMaxInitRtrAdvertisements = 3, // The maximum number of initial Router Advertisements. + kMaxRtrSolicitations = 3, // The Maximum number of Router Solicitations before sending Router Advertisements. + }; + + void Start(void); + void Stop(void); + void HandleNotifierEvents(Events aEvents); + bool IsInitialized(void) const { return mInfraIfIndex != 0; } + otError LoadOrGenerateRandomOmrPrefix(void); + otError LoadOrGenerateRandomOnLinkPrefix(void); + + /** + * This method tells whether the first prefix is numerically smaller than the second one. + * + * @note The caller must guarantee that the two prefix has the same length. + * + */ + static bool IsPrefixSmallerThan(const Ip6::Prefix &aFirstPrefix, const Ip6::Prefix &aSecondPrefix); + + static bool IsValidOmrPrefix(const Ip6::Prefix &aOmrPrefix); + static bool IsValidOnLinkPrefix(const Ip6::Prefix &aOnLinkPrefix); + + /** + * This method evaluate the routing policy depends on prefix and route + * information on Thread Network and infra link. As a result, this + * method May send RA messages on infra link and publish/unpublish + * OMR prefix in the Thread network. + * + * @sa EvaluateOmrPrefix + * @sa EvaluateOnLinkPrefix + * @sa PublishLocalOmrPrefix + * @sa UnpublishLocalOmrPrefix + * + */ + void EvaluateRoutingPolicy(void); + + /** + * This method evaluates the OMR prefix for the Thread Network. + * + * @param[out] aNewOmrPrefixes An array of the new OMR prefixes should be advertised. + * MUST not be nullptr. + * @param[in] aMaxOmrPrefixNum The maximum number of OMR prefixes that @p aNewOmrPrefixes can hold. + * + * @returns The number of the new OMR prefixes that should be advertised after this evaluation. + * + */ + uint8_t EvaluateOmrPrefix(Ip6::Prefix *aNewOmrPrefixes, uint8_t aMaxOmrPrefixNum); + + /** + * This method evaluates the on-link prefix for the infra link. + * + * @returns A pointer to the new on-link prefix should be advertised. + * nullptr if we should no longer advertise an on-link prefix. + * + */ + const Ip6::Prefix *EvaluateOnLinkPrefix(void) const; + + /** + * This method publishes the local OMR prefix in Thread network. + * + */ + otError PublishLocalOmrPrefix(void); + + /** + * This method unpublishes the local OMR prefix. + * + */ + void UnpublishLocalOmrPrefix(void); + + /** + * This method starts sending Router Solicitations in random delay + * between 0 and kMaxRtrSolicitationDelay. + * + */ + void StartRouterSolicitation(void); + + /** + * This method sends Router Solicitation messages to discover on-link + * prefix on infra links. + * + * @sa HandleRouterAdvertisement + * + * @retval OT_ERROR_NONE Successfully sent the message. + * @retval OT_ERROR_FAILED Failed to send the message. + * + */ + otError SendRouterSolicitation(void); + + /** + * This method sends Router Advertisement messages to advertise + * on-link prefix and route for OMR prefix. + * + * @param[in] aNewOmrPrefixes A pointer to an array of the new OMR prefixes to be advertised. + * @p aNewOmrPrefixNum must be zero if this argument is nullptr. + * @param[in] aNewOmrPrefixNum The number of the new OMR prefixes to be advertised. + * Zero means we should stop advertising OMR prefixes. + * @param[in] aOnLinkPrefix A pointer to the new on-link prefix to be advertised. + * nullptr means we should stop advertising on-link prefix. + * + */ + void SendRouterAdvertisement(const Ip6::Prefix *aNewOmrPrefixes, + uint8_t aNewOmrPrefixNum, + const Ip6::Prefix *aNewOnLinkPrefix); + + static void HandleRouterAdvertisementTimer(Timer &aTimer); + void HandleRouterAdvertisementTimer(void); + + static void HandleRouterSolicitTimer(Timer &aTimer); + void HandleRouterSolicitTimer(void); + + static void HandleDiscoveredOnLinkPrefixInvalidTimer(Timer &aTimer); + void HandleDiscoveredOnLinkPrefixInvalidTimer(void); + + void HandleRouterSolicit(const Ip6::Address &aSrcAddress, const uint8_t *aBuffer, uint16_t aBufferLength); + void HandleRouterAdvertisement(const Ip6::Address &aSrcAddress, const uint8_t *aBuffer, uint16_t aBufferLength); + + static bool ContainsPrefix(const Ip6::Prefix &aPrefix, const Ip6::Prefix *aPrefixList, uint8_t aPrefixNum); + + static uint32_t GetPrefixExpireDelay(uint32_t aValidLifetime); + + bool mIsRunning; + uint32_t mInfraIfIndex; + + /** + * The OMR prefix loaded from local persistent storage or randomly generated + * if non is found in persistent storage. + * + */ + Ip6::Prefix mLocalOmrPrefix; + + /** + * The advertised OMR prefixes. + * + */ + Ip6::Prefix mAdvertisedOmrPrefixes[kMaxOmrPrefixNum]; + uint8_t mAdvertisedOmrPrefixNum; + + /** + * The on-link prefix loaded from local persistent storage or randomly generated + * if non is found in persistent storage. + * + */ + Ip6::Prefix mLocalOnLinkPrefix; + + /** + * The advertised on-link prefix. + * + * Could only be nullptr or a pointer to mLocalOnLinkPrefix. + * + */ + const Ip6::Prefix *mAdvertisedOnLinkPrefix; + + /** + * The on-link prefix we discovered on the infra link. + * + */ + Ip6::Prefix mDiscoveredOnLinkPrefix; + + TimerMilli mRouterAdvertisementTimer; + uint32_t mRouterAdvertisementCount; + + TimerMilli mRouterSolicitTimer; + uint8_t mRouterSolicitCount; + + TimerMilli mDiscoveredOnLinkPrefixInvalidTimer; +}; + +} // namespace BorderRouter + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + +#endif // ROUTING_MANAGER_HPP_ diff --git a/src/core/common/instance.cpp b/src/core/common/instance.cpp index 440dcdbec..95f80e013 100644 --- a/src/core/common/instance.cpp +++ b/src/core/common/instance.cpp @@ -93,6 +93,9 @@ Instance::Instance(void) #if OPENTHREAD_CONFIG_OTNS_ENABLE , mOtns(*this) #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + , mRoutingManager(*this) +#endif #endif // OPENTHREAD_MTD || OPENTHREAD_FTD #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE , mLinkRaw(*this) diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index 2cb432475..78f5472dc 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -95,6 +95,10 @@ #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +#include "border_router/routing_manager.hpp" +#endif + #endif // OPENTHREAD_FTD || OPENTHREAD_MTD #if OPENTHREAD_ENABLE_VENDOR_EXTENSION #include "common/extension.hpp" @@ -391,6 +395,10 @@ private: Utils::Otns mOtns; #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + BorderRouter::RoutingManager mRoutingManager; +#endif + #endif // OPENTHREAD_MTD || OPENTHREAD_FTD #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE Mac::LinkRaw mLinkRaw; @@ -847,6 +855,13 @@ template <> inline Utils::Otns &Instance::Get(void) } #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +template <> inline BorderRouter::RoutingManager &Instance::Get(void) +{ + return mRoutingManager; +} +#endif + #endif // OPENTHREAD_MTD || OPENTHREAD_FTD #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE diff --git a/src/core/common/logging.hpp b/src/core/common/logging.hpp index 2dd5a4ed9..460633b59 100644 --- a/src/core/common/logging.hpp +++ b/src/core/common/logging.hpp @@ -94,6 +94,7 @@ extern "C" { #define _OT_REGION_BBR_PREFIX "-BBR-----: " #define _OT_REGION_MLR_PREFIX "-MLR-----: " #define _OT_REGION_DUA_PREFIX "-DUA-----: " +#define _OT_REGION_BR_PREFIX "-BR------: " #else #define _OT_REGION_API_PREFIX _OT_REGION_SUFFIX #define _OT_REGION_MLE_PREFIX _OT_REGION_SUFFIX @@ -114,6 +115,7 @@ extern "C" { #define _OT_REGION_BBR_PREFIX _OT_REGION_SUFFIX #define _OT_REGION_MLR_PREFIX _OT_REGION_SUFFIX #define _OT_REGION_DUA_PREFIX _OT_REGION_SUFFIX +#define _OT_REGION_BR_PREFIX _OT_REGION_SUFFIX #endif /** @@ -329,6 +331,64 @@ void otLogDebg(otLogRegion aRegion, const char *aRegionPrefix, const char *aForm #define otLogInfoMbedTls(...) otLogInfoMeshCoP(__VA_ARGS__) #define otLogDebgMbedTls(...) otLogDebgMeshCoP(__VA_ARGS__) +/** + * @def otLogCritBr + * + * This function generates a log with level critical for the BR region. + * + * @param[in] ... Arguments for the format specification. + * + */ + +/** + * @def otLogWarnBr + * + * This function generates a log with level warning for the BR region. + * + * @param[in] ... Arguments for the format specification. + * + */ + +/** + * @def otLogNoteBr + * + * This function generates a log with level note for the BR region. + * + * @param[in] ... Arguments for the format specification. + * + */ + +/** + * @def otLogInfoBr + * + * This function generates a log with level info for the BR region. + * + * @param[in] ... Arguments for the format specification. + * + */ + +/** + * @def otLogDebgBr + * + * This function generates a log with level debug for the BR region. + * + * @param[in] ... Arguments for the format specification. + * + */ +#if OPENTHREAD_CONFIG_LOG_BR +#define otLogCritBr(...) otLogCrit(OT_LOG_REGION_BR, _OT_REGION_BR_PREFIX, __VA_ARGS__) +#define otLogWarnBr(...) otLogWarn(OT_LOG_REGION_BR, _OT_REGION_BR_PREFIX, __VA_ARGS__) +#define otLogNoteBr(...) otLogNote(OT_LOG_REGION_BR, _OT_REGION_BR_PREFIX, __VA_ARGS__) +#define otLogInfoBr(...) otLogInfo(OT_LOG_REGION_BR, _OT_REGION_BR_PREFIX, __VA_ARGS__) +#define otLogDebgBr(...) otLogDebg(OT_LOG_REGION_BR, _OT_REGION_BR_PREFIX, __VA_ARGS__) +#else +#define otLogCritBr(...) +#define otLogWarnBr(...) +#define otLogNoteBr(...) +#define otLogInfoBr(...) +#define otLogDebgBr(...) +#endif + /** * @def otLogCritMle * diff --git a/src/core/common/notifier.cpp b/src/core/common/notifier.cpp index 8b0be4b70..63ff56468 100644 --- a/src/core/common/notifier.cpp +++ b/src/core/common/notifier.cpp @@ -33,6 +33,7 @@ #include "notifier.hpp" +#include "border_router/routing_manager.hpp" #include "common/code_utils.hpp" #include "common/debug.hpp" #include "common/locator-getters.hpp" @@ -179,6 +180,9 @@ void Notifier::EmitEvents(void) #if OPENTHREAD_ENABLE_VENDOR_EXTENSION Get().HandleNotifierEvents(events); #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + Get().HandleNotifierEvents(events); +#endif for (ExternalCallback &callback : mExternalCallbacks) { diff --git a/src/core/common/settings.cpp b/src/core/common/settings.cpp index 44491d823..fc408261d 100644 --- a/src/core/common/settings.cpp +++ b/src/core/common/settings.cpp @@ -81,6 +81,12 @@ void SettingsBase::LogDadInfo(const char *aAction, const DadInfo &aDadInfo) cons otLogInfoCore("Non-volatile: %s DadInfo {DadCounter:%2d}", aAction, aDadInfo.GetDadCounter()); } #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +void SettingsBase::LogPrefix(const char *aAction, const char *aPrefixName, const Ip6::Prefix &aOmrPrefix) const +{ + otLogInfoCore("Non-volatile: %s %s %s", aAction, aPrefixName, aOmrPrefix.ToString().AsCString()); +} +#endif #endif // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) @@ -440,6 +446,76 @@ exit: } #endif // OPENTHREAD_CONFIG_DUA_ENABLE +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +otError Settings::SaveOmrPrefix(const Ip6::Prefix &aOmrPrefix) +{ + otError error = OT_ERROR_NONE; + Ip6::Prefix prevOmrPrefix; + uint16_t length = sizeof(prevOmrPrefix); + + if ((Read(kKeyOmrPrefix, &prevOmrPrefix, length) == OT_ERROR_NONE) && (length == sizeof(prevOmrPrefix)) && + (prevOmrPrefix == aOmrPrefix)) + { + LogPrefix("Re-saved", "OMR prefix", aOmrPrefix); + ExitNow(); + } + + SuccessOrExit(error = Save(kKeyOmrPrefix, &aOmrPrefix, sizeof(aOmrPrefix))); + LogPrefix("Saved", "OMR prefix", aOmrPrefix); + +exit: + LogFailure(error, "saving OMR prefix", false); + return error; +} + +otError Settings::ReadOmrPrefix(Ip6::Prefix &aOmrPrefix) const +{ + otError error; + uint16_t length = sizeof(aOmrPrefix); + + aOmrPrefix.Clear(); + SuccessOrExit(error = Read(kKeyOmrPrefix, &aOmrPrefix, length)); + LogPrefix("Read", "OMR prefix", aOmrPrefix); + +exit: + return error; +} + +otError Settings::SaveOnLinkPrefix(const Ip6::Prefix &aOnLinkPrefix) +{ + otError error = OT_ERROR_NONE; + Ip6::Prefix prevOnLinkPrefix; + uint16_t length = sizeof(prevOnLinkPrefix); + + if ((Read(kKeyOnLinkPrefix, &prevOnLinkPrefix, length) == OT_ERROR_NONE) && (length == sizeof(prevOnLinkPrefix)) && + (prevOnLinkPrefix == aOnLinkPrefix)) + { + LogPrefix("Re-saved", "on-link prefix", aOnLinkPrefix); + ExitNow(); + } + + SuccessOrExit(error = Save(kKeyOnLinkPrefix, &aOnLinkPrefix, sizeof(aOnLinkPrefix))); + LogPrefix("Saved", "on-link prefix", aOnLinkPrefix); + +exit: + LogFailure(error, "saving on-link prefix", false); + return error; +} + +otError Settings::ReadOnLinkPrefix(Ip6::Prefix &aOnLinkPrefix) const +{ + otError error; + uint16_t length = sizeof(aOnLinkPrefix); + + aOnLinkPrefix.Clear(); + SuccessOrExit(error = Read(kKeyOnLinkPrefix, &aOnLinkPrefix, length)); + LogPrefix("Read", "on-link prefix", aOnLinkPrefix); + +exit: + return error; +} +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + otError Settings::Read(Key aKey, void *aBuffer, uint16_t &aSize) const { return Get().Get(aKey, 0, reinterpret_cast(aBuffer), &aSize); diff --git a/src/core/common/settings.hpp b/src/core/common/settings.hpp index 2fe11c4b2..8a4449e6d 100644 --- a/src/core/common/settings.hpp +++ b/src/core/common/settings.hpp @@ -589,6 +589,8 @@ public: kKeyReserved = 0x0006, ///< Reserved (previously auto-start) kKeySlaacIidSecretKey = 0x0007, ///< Secret key used by SLAAC module for generating semantically opaque IID kKeyDadInfo = 0x0008, ///< Duplicate Address Detection (DAD) information. + kKeyOmrPrefix = 0x0009, ///< Off-mesh routable (OMR) prefix. + kKeyOnLinkPrefix = 0x000a, ///< On-link prefix for infrastructure link. }; protected: @@ -604,6 +606,9 @@ protected: #if OPENTHREAD_CONFIG_DUA_ENABLE void LogDadInfo(const char *aAction, const DadInfo &aDadInfo) const; #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + void LogPrefix(const char *aAction, const char *aPrefixName, const Ip6::Prefix &aOmrPrefix) const; +#endif #else // (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_UTIL != 0) void LogNetworkInfo(const char *, const NetworkInfo &) const {} void LogParentInfo(const char *, const ParentInfo &) const {} @@ -611,6 +616,9 @@ protected: #if OPENTHREAD_CONFIG_DUA_ENABLE void LogDadInfo(const char *, const DadInfo &) const {} #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + void LogPrefix(const char *, const char *, const Ip6::Prefix &) const {} +#endif #endif // (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_UTIL != 0) #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_WARN) && (OPENTHREAD_CONFIG_LOG_UTIL != 0) @@ -1000,6 +1008,54 @@ public: #endif // OPENTHREAD_CONFIG_DUA_ENABLE +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + /** + * This method saves OMR prefix. + * + * @param[in] aOmrPrefix An OMR prefix to be saved. + * + * @retval OT_ERROR_NONE Successfully saved the OMR prefix in settings. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError SaveOmrPrefix(const Ip6::Prefix &aOmrPrefix); + + /** + * This method reads OMR prefix. + * + * @param[out] aOmrPrefix A reference to a `Ip6::Prefix` structure to output the OMR prefix. + * + * @retval OT_ERROR_NONE Successfully read the OMR prefix. + * @retval OT_ERROR_NOT_FOUND No corresponding value in the setting store. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError ReadOmrPrefix(Ip6::Prefix &aOmrPrefix) const; + + /** + * This method saves on-link prefix. + * + * @param[in] aOnLinkPrefix An on-link prefix to be saved. + * + * @retval OT_ERROR_NONE Successfully saved the on-link prefix in settings. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError SaveOnLinkPrefix(const Ip6::Prefix &aOnLinkPrefix); + + /** + * This method reads on-link prefix. + * + * @param[out] aOnLinkPrefix A reference to a `Ip6::Prefix` structure to output the on-link prefix. + * + * @retval OT_ERROR_NONE Successfully read the on-link prefix. + * @retval OT_ERROR_NOT_FOUND No corresponding value in the setting store. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError ReadOnLinkPrefix(Ip6::Prefix &aOnLinkPrefix) const; +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + private: class ChildInfoIteratorBuilder : public InstanceLocator { diff --git a/src/core/config/border_router.h b/src/core/config/border_router.h index 2284367f7..bdddb0e34 100644 --- a/src/core/config/border_router.h +++ b/src/core/config/border_router.h @@ -55,4 +55,14 @@ #define OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + * + * Define to 1 to enable (Duckhorn) Border Routing support. + * + */ +#ifndef OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +#define OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE 0 +#endif + #endif // CONFIG_BORDER_ROUTER_H_ diff --git a/src/core/config/logging.h b/src/core/config/logging.h index f8e3b0a1c..7d4a912be 100644 --- a/src/core/config/logging.h +++ b/src/core/config/logging.h @@ -299,6 +299,16 @@ #define OPENTHREAD_CONFIG_LOG_DUA 1 #endif +/** + * @def OPENTHREAD_CONFIG_LOG_BR + * + * Define to Border Router (BR) region logging. + * + */ +#ifndef OPENTHREAD_CONFIG_LOG_BR +#define OPENTHREAD_CONFIG_LOG_BR 1 +#endif + /** * @def OPENTHREAD_CONFIG_LOG_PREPEND_LEVEL * diff --git a/src/core/config/openthread-core-config-check.h b/src/core/config/openthread-core-config-check.h index 0b8253191..6a0c982b3 100644 --- a/src/core/config/openthread-core-config-check.h +++ b/src/core/config/openthread-core-config-check.h @@ -507,6 +507,17 @@ #endif // OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +#if !OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE +#error "OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE is required for OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE" +#endif + +#if !OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE +#error "OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE is required for OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE" +#endif + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE #if (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2) #error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE" diff --git a/src/core/net/icmp6.hpp b/src/core/net/icmp6.hpp index 592d4f047..30bee8d12 100644 --- a/src/core/net/icmp6.hpp +++ b/src/core/net/icmp6.hpp @@ -87,6 +87,8 @@ public: kTypeParameterProblem = OT_ICMP6_TYPE_PARAMETER_PROBLEM, ///< Parameter Problem kTypeEchoRequest = OT_ICMP6_TYPE_ECHO_REQUEST, ///< Echo Request kTypeEchoReply = OT_ICMP6_TYPE_ECHO_REPLY, ///< Echo Reply + kTypeRouterSolicit = OT_ICMP6_TYPE_ROUTER_SOLICIT, ///< Router Solicitation + kTypeRouterAdvert = OT_ICMP6_TYPE_ROUTER_ADVERT, ///< Router Advertisement }; /** @@ -195,7 +197,6 @@ public: * */ void SetSequence(uint16_t aSequence) { mData.m16[1] = HostSwap16(aSequence); } - } OT_TOOL_PACKED_END; /** diff --git a/src/core/net/ip6_address.hpp b/src/core/net/ip6_address.hpp index 00c82a096..1805f24ce 100644 --- a/src/core/net/ip6_address.hpp +++ b/src/core/net/ip6_address.hpp @@ -87,7 +87,7 @@ public: * */ OT_TOOL_PACKED_BEGIN -class Prefix : public otIp6Prefix +class Prefix : public otIp6Prefix, public Clearable { public: enum : uint8_t diff --git a/src/lib/spinel/spinel.h b/src/lib/spinel/spinel.h index 61a037a03..3c9b5ac9a 100644 --- a/src/lib/spinel/spinel.h +++ b/src/lib/spinel/spinel.h @@ -692,6 +692,7 @@ enum SPINEL_NCP_LOG_REGION_OT_BBR = 17, SPINEL_NCP_LOG_REGION_OT_MLR = 18, SPINEL_NCP_LOG_REGION_OT_DUA = 19, + SPINEL_NCP_LOG_REGION_OT_BR = 20, }; enum diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp index 9723e0ba1..277080e19 100644 --- a/src/ncp/ncp_base.cpp +++ b/src/ncp/ncp_base.cpp @@ -624,6 +624,9 @@ unsigned int NcpBase::ConvertLogRegion(otLogRegion aLogRegion) case OT_LOG_REGION_DUA: spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_DUA; break; + case OT_LOG_REGION_BR: + spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_BR; + break; } return spinelLogRegion; diff --git a/src/posix/platform/CMakeLists.txt b/src/posix/platform/CMakeLists.txt index c90f340dc..4f496352e 100644 --- a/src/posix/platform/CMakeLists.txt +++ b/src/posix/platform/CMakeLists.txt @@ -64,6 +64,7 @@ add_library(openthread-posix backbone.cpp entropy.cpp hdlc_interface.cpp + infra_if.cpp logging.cpp misc.cpp multicast_routing.cpp diff --git a/src/posix/platform/Makefile.am b/src/posix/platform/Makefile.am index 928528497..3c0725c06 100644 --- a/src/posix/platform/Makefile.am +++ b/src/posix/platform/Makefile.am @@ -48,6 +48,7 @@ libopenthread_posix_a_SOURCES = \ backbone.cpp \ entropy.cpp \ hdlc_interface.cpp \ + infra_if.cpp \ logging.cpp \ misc.cpp \ multicast_routing.cpp \ diff --git a/src/posix/platform/infra_if.cpp b/src/posix/platform/infra_if.cpp new file mode 100644 index 000000000..8184b522a --- /dev/null +++ b/src/posix/platform/infra_if.cpp @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements the infrastructure interface for posix. + */ + +#include "platform-posix.h" + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + +#ifdef __APPLE__ +#define __APPLE_USE_RFC_3542 +#endif + +#include +#include +// clang-format off +#include +#include +// clang-format on +#include +#include + +#include + +#include "common/code_utils.hpp" + +static char sInfraIfName[IFNAMSIZ]; +static uint32_t sInfraIfIndex = 0; +static int sInfraIfIcmp6Socket = -1; +static otIp6Address sInfraIfLinkLocalAddr; + +otError otPlatInfraIfSendIcmp6Nd(uint32_t aInfraIfIndex, + const otIp6Address *aDestAddress, + const uint8_t * aBuffer, + uint16_t aBufferLength) +{ + otError error = OT_ERROR_NONE; + + struct iovec iov; + struct in6_pktinfo *packetInfo; + + int hopLimit = 255; + uint8_t cmsgBuffer[CMSG_SPACE(sizeof(*packetInfo)) + CMSG_SPACE(sizeof(hopLimit))]; + struct msghdr msgHeader; + struct cmsghdr * cmsgPointer; + ssize_t rval; + struct sockaddr_in6 dest; + + VerifyOrExit(sInfraIfIcmp6Socket >= 0, error = OT_ERROR_FAILED); + VerifyOrExit(aInfraIfIndex == sInfraIfIndex, error = OT_ERROR_DROP); + + memset(cmsgBuffer, 0, sizeof(cmsgBuffer)); + + // Send the message + memset(&dest, 0, sizeof(dest)); + dest.sin6_family = AF_INET6; + memcpy(&dest.sin6_addr, aDestAddress, sizeof(*aDestAddress)); + if (IN6_IS_ADDR_LINKLOCAL(&dest.sin6_addr) || IN6_IS_ADDR_MC_LINKLOCAL(&dest.sin6_addr)) + { + dest.sin6_scope_id = sInfraIfIndex; + } + + iov.iov_base = const_cast(aBuffer); + iov.iov_len = aBufferLength; + + msgHeader.msg_namelen = sizeof(dest); + msgHeader.msg_name = &dest; + msgHeader.msg_iov = &iov; + msgHeader.msg_iovlen = 1; + msgHeader.msg_control = cmsgBuffer; + msgHeader.msg_controllen = sizeof(cmsgBuffer); + + // Specify the interface. + cmsgPointer = CMSG_FIRSTHDR(&msgHeader); + cmsgPointer->cmsg_level = IPPROTO_IPV6; + cmsgPointer->cmsg_type = IPV6_PKTINFO; + cmsgPointer->cmsg_len = CMSG_LEN(sizeof(*packetInfo)); + packetInfo = (struct in6_pktinfo *)CMSG_DATA(cmsgPointer); + memset(packetInfo, 0, sizeof(*packetInfo)); + packetInfo->ipi6_ifindex = sInfraIfIndex; + + // Per section 6.1.2 of RFC 4861, we need to send the ICMPv6 message with IP Hop Limit 255. + cmsgPointer = CMSG_NXTHDR(&msgHeader, cmsgPointer); + cmsgPointer->cmsg_level = IPPROTO_IPV6; + cmsgPointer->cmsg_type = IPV6_HOPLIMIT; + cmsgPointer->cmsg_len = CMSG_LEN(sizeof(hopLimit)); + memcpy(CMSG_DATA(cmsgPointer), &hopLimit, sizeof(hopLimit)); + + rval = sendmsg(sInfraIfIcmp6Socket, &msgHeader, 0); + if (rval < 0) + { + otLogWarnPlat("failed to send ICMPv6 message: %s", strerror(errno)); + ExitNow(error = OT_ERROR_FAILED); + } + + if (static_cast(rval) != iov.iov_len) + { + otLogWarnPlat("failed to send ICMPv6 message: partially sent"); + ExitNow(error = OT_ERROR_FAILED); + } + +exit: + return error; +} + +static void InitLinkLocalAddress(void) +{ + struct ifaddrs *ifAddrs = nullptr; + + if (getifaddrs(&ifAddrs) < 0) + { + otLogCritPlat("failed to get netif addresses: %s", strerror(errno)); + DieNow(OT_EXIT_ERROR_ERRNO); + } + + for (struct ifaddrs *addr = ifAddrs; addr != nullptr; addr = addr->ifa_next) + { + struct sockaddr_in6 *ip6Addr; + + if (strncmp(addr->ifa_name, sInfraIfName, sizeof(sInfraIfName)) != 0 || addr->ifa_addr->sa_family != AF_INET6) + { + continue; + } + + ip6Addr = reinterpret_cast(addr->ifa_addr); + if (IN6_IS_ADDR_LINKLOCAL(&ip6Addr->sin6_addr)) + { + memcpy(&sInfraIfLinkLocalAddr, &ip6Addr->sin6_addr, sizeof(sInfraIfLinkLocalAddr)); + break; + } + } + + freeifaddrs(ifAddrs); +} + +void platformInfraIfInit(otInstance *aInstance, const char *aIfName) +{ + OT_UNUSED_VARIABLE(aInstance); + + int sock; + struct icmp6_filter filter; + ssize_t rval; + const int kEnable = 1; + const int kIpv6ChecksumOffset = 2; + const int kHopLimit = 255; + uint32_t ifIndex = 0; + + if (strlen(aIfName) >= sizeof(sInfraIfName)) + { + otLogCritPlat("infra interface name '%s' is too long", aIfName); + DieNow(OT_EXIT_INVALID_ARGUMENTS); + } + strcpy(sInfraIfName, aIfName); + + // Initializes the infra interface. + ifIndex = if_nametoindex(aIfName); + if (ifIndex == 0) + { + otLogCritPlat("failed to get the index for infra interface %s: %s", aIfName, strerror(errno)); + DieNow(OT_EXIT_ERROR_ERRNO); + } + sInfraIfIndex = ifIndex; + + // Initializes the ICMPv6 socket. + sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + if (sock < 0) + { + otLogCritPlat("failed to open ICMPv6 socket: %s", strerror(errno)); + DieNow(OT_EXIT_ERROR_ERRNO); + } + + // Only accept router advertisements and router solicits. + ICMP6_FILTER_SETBLOCKALL(&filter); + ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); + ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); + + rval = setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)); + if (rval < 0) + { + otLogCritPlat("Can't set ICMP6_FILTER: %s", strerror(errno)); + DieNow(OT_EXIT_ERROR_ERRNO); + } + + // We want a source address and interface index. + rval = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &kEnable, sizeof(kEnable)); + if (rval < 0) + { + otLogCritPlat("Can't set IPV6_RECVPKTINFO: %s", strerror(errno)); + DieNow(OT_EXIT_ERROR_ERRNO); + } + +#ifdef __linux__ + rval = setsockopt(sock, IPPROTO_RAW, IPV6_CHECKSUM, &kIpv6ChecksumOffset, sizeof(kIpv6ChecksumOffset)); +#else + rval = setsockopt(sock, IPPROTO_IPV6, IPV6_CHECKSUM, &kIpv6ChecksumOffset, sizeof(kIpv6ChecksumOffset)); +#endif + if (rval < 0) + { + otLogCritPlat("Can't set IPV6_CHECKSUM: %s", strerror(errno)); + DieNow(OT_EXIT_ERROR_ERRNO); + } + + // We need to be able to reject RAs arriving from off-link. + rval = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &kEnable, sizeof(kEnable)); + if (rval < 0) + { + otLogCritPlat("Can't set IPV6_RECVHOPLIMIT: %s", strerror(errno)); + DieNow(OT_EXIT_ERROR_ERRNO); + } + + rval = setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &kHopLimit, sizeof(kHopLimit)); + if (rval < 0) + { + otLogCritPlat("Can't set IPV6_UNICAST_HOPS: %s", strerror(errno)); + DieNow(OT_EXIT_ERROR_ERRNO); + } + + rval = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &kHopLimit, sizeof(kHopLimit)); + if (rval < 0) + { + otLogCritPlat("Can't set IPV6_MULTICAST_HOPS: %s", strerror(errno)); + DieNow(OT_EXIT_ERROR_ERRNO); + } + + sInfraIfIcmp6Socket = sock; + InitLinkLocalAddress(); +} + +void platformInfraIfDeinit(void) +{ + if (sInfraIfIcmp6Socket != -1) + { + close(sInfraIfIcmp6Socket); + sInfraIfIcmp6Socket = -1; + } + + sInfraIfIndex = 0; +} + +void platformInfraIfUpdateFdSet(fd_set &aReadFdSet, int &aMaxFd) +{ + VerifyOrExit(sInfraIfIcmp6Socket != -1); + + FD_SET(sInfraIfIcmp6Socket, &aReadFdSet); + aMaxFd = OT_MAX(aMaxFd, sInfraIfIcmp6Socket); + +exit: + return; +} + +void platformInfraIfProcess(otInstance *aInstance, const fd_set &aReadFdSet) +{ + otError error = OT_ERROR_DROP; + uint8_t buffer[1500]; + uint16_t bufferLength; + + ssize_t rval; + struct msghdr msg; + struct iovec bufp; + char cmsgbuf[128]; + struct cmsghdr *cmh; + uint32_t ifIndex = 0; + int hopLimit = -1; + + struct sockaddr_in6 srcAddr; + struct in6_addr dstAddr; + + VerifyOrExit(sInfraIfIcmp6Socket != -1); + VerifyOrExit(FD_ISSET(sInfraIfIcmp6Socket, &aReadFdSet)); + + memset(&srcAddr, 0, sizeof(srcAddr)); + memset(&dstAddr, 0, sizeof(dstAddr)); + + bufp.iov_base = buffer; + bufp.iov_len = sizeof(buffer); + msg.msg_iov = &bufp; + msg.msg_iovlen = 1; + msg.msg_name = &srcAddr; + msg.msg_namelen = sizeof(srcAddr); + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + + rval = recvmsg(sInfraIfIcmp6Socket, &msg, 0); + if (rval < 0) + { + otLogWarnPlat("failed to receive ICMPv6 message: %s", strerror(errno)); + ExitNow(); + } + + bufferLength = static_cast(rval); + + for (cmh = CMSG_FIRSTHDR(&msg); cmh; cmh = CMSG_NXTHDR(&msg, cmh)) + { + if (cmh->cmsg_level == IPPROTO_IPV6 && cmh->cmsg_type == IPV6_PKTINFO && + cmh->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) + { + struct in6_pktinfo pktinfo; + + memcpy(&pktinfo, CMSG_DATA(cmh), sizeof pktinfo); + ifIndex = pktinfo.ipi6_ifindex; + dstAddr = pktinfo.ipi6_addr; + } + else if (cmh->cmsg_level == IPPROTO_IPV6 && cmh->cmsg_type == IPV6_HOPLIMIT && + cmh->cmsg_len == CMSG_LEN(sizeof(int))) + { + hopLimit = *(int *)CMSG_DATA(cmh); + } + } + + VerifyOrExit(ifIndex == sInfraIfIndex); + + // We currently accept only RA & RS messages for the Border Router and it requires that + // the hoplimit must be 255 and the source address must be a link-local address. + VerifyOrExit(hopLimit == 255 && IN6_IS_ADDR_LINKLOCAL(&srcAddr.sin6_addr)); + + // Drop multicast messages sent by ourselves. + VerifyOrExit(!otIp6IsAddressEqual(&sInfraIfLinkLocalAddr, reinterpret_cast(&srcAddr.sin6_addr))); + otPlatInfraIfRecvIcmp6Nd(aInstance, ifIndex, reinterpret_cast(&srcAddr.sin6_addr), buffer, + bufferLength); + + error = OT_ERROR_NONE; + +exit: + if (error != OT_ERROR_NONE) + { + otLogDebgPlat("drop ICMPv6 message"); + } +} + +uint32_t platformInfraIfGetIndex(void) +{ + return sInfraIfIndex; +} +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE diff --git a/src/posix/platform/platform-posix.h b/src/posix/platform/platform-posix.h index ff019479e..5e4e3dd2e 100644 --- a/src/posix/platform/platform-posix.h +++ b/src/posix/platform/platform-posix.h @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -498,6 +499,47 @@ extern char gBackboneNetifName[IFNAMSIZ]; */ extern unsigned int gBackboneNetifIndex; +/** + * This function initializes the infrastructure interface. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aIfName The name of the infrastructure interface. + * + */ +void platformInfraIfInit(otInstance *aInstance, const char *aIfName); + +/** + * This function deinitializes the infrastructure interface. + * + */ +void platformInfraIfDeinit(void); + +/** + * This function updates the read fd set. + * + * @param[out] aReadFdSet The fd set to be updated. + * @param[out] aMaxFd The maximum fd to be updated. + * + */ +void platformInfraIfUpdateFdSet(fd_set &aReadFdSet, int &aMaxFd); + +/** + * This function processes possible events on the infrastructure interface. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aReadFdSet The fd set which may contain read vents. + * + */ +void platformInfraIfProcess(otInstance *aInstance, const fd_set &aReadFdSet); + +/** + * This function returns the index of the infrastructure interface. + * + * @returns The index of the infrastructure interface. 0 indicates invalid index. + * + */ +uint32_t platformInfraIfGetIndex(void); + #ifdef __cplusplus } #endif diff --git a/src/posix/platform/system.cpp b/src/posix/platform/system.cpp index 1824fe1b4..ede5fdfb2 100644 --- a/src/posix/platform/system.cpp +++ b/src/posix/platform/system.cpp @@ -38,6 +38,7 @@ #include #include +#include #include #include #include @@ -97,6 +98,11 @@ otInstance *otSysInit(otPlatformConfig *aPlatformConfig) platformBackboneInit(instance, aPlatformConfig->mBackboneInterfaceName); #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + // Reuse the backbone interface name. + platformInfraIfInit(instance, aPlatformConfig->mBackboneInterfaceName); +#endif + #if OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE platformNetifInit(instance, aPlatformConfig->mInterfaceName); #elif OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE @@ -107,6 +113,10 @@ otInstance *otSysInit(otPlatformConfig *aPlatformConfig) SuccessOrDie(otSetStateChangedCallback(instance, processStateChange, instance)); #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + SuccessOrDie(otBorderRoutingInit(instance, platformInfraIfGetIndex())); +#endif + return instance; } @@ -123,6 +133,10 @@ void otSysDeinit(void) platformTrelDeinit(); #endif IgnoreError(otPlatUartDisable()); + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + platformInfraIfDeinit(); +#endif } #if OPENTHREAD_POSIX_VIRTUAL_TIME @@ -173,6 +187,9 @@ void otSysMainloopUpdate(otInstance *aInstance, otSysMainloopContext *aMainloop) #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE platformBackboneUpdateFdSet(aMainloop->mReadFdSet, aMainloop->mMaxFd); #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + platformInfraIfUpdateFdSet(aMainloop->mReadFdSet, aMainloop->mMaxFd); +#endif #if OPENTHREAD_POSIX_VIRTUAL_TIME virtualTimeUpdateFdSet(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mErrorFdSet, &aMainloop->mMaxFd, &aMainloop->mTimeout); @@ -254,6 +271,9 @@ void otSysMainloopProcess(otInstance *aInstance, const otSysMainloopContext *aMa #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE platformBackboneProcess(aMainloop->mReadFdSet); #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + platformInfraIfProcess(aInstance, aMainloop->mReadFdSet); +#endif } #if OPENTHREAD_CONFIG_OTNS_ENABLE diff --git a/tests/scripts/thread-cert/border_routing/test_multi_border_routers.py b/tests/scripts/thread-cert/border_routing/test_multi_border_routers.py new file mode 100755 index 000000000..2e96ba9aa --- /dev/null +++ b/tests/scripts/thread-cert/border_routing/test_multi_border_routers.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +import logging +import unittest + +import config +import thread_cert + +# Test description: +# This test verifies that a single OMR and on-link prefix is chosen +# and advertised when there are multiple Border Routers in the same +# Thread and infrastructure network. +# +# Topology: +# ----------------(eth)------------------ +# | | | +# BR1 (Leader) ----- BR2 HOST +# | +# ED1 +# + +BR1 = 1 +ROUTER1 = 2 +BR2 = 3 +HOST = 4 + +CHANNEL1 = 18 + + +class MultiBorderRouters(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + + TOPOLOGY = { + BR1: { + 'name': 'BR_1', + 'allowlist': [ROUTER1, BR2], + 'is_otbr': True, + 'version': '1.2', + 'channel': CHANNEL1, + 'router_selection_jitter': 2, + }, + ROUTER1: { + 'name': 'Router_1', + 'allowlist': [BR1], + 'version': '1.2', + 'channel': CHANNEL1, + 'router_selection_jitter': 2, + }, + BR2: { + 'name': 'BR_2', + 'allowlist': [BR1], + 'is_otbr': True, + 'version': '1.2', + 'channel': CHANNEL1, + 'router_selection_jitter': 2, + }, + HOST: { + 'name': 'Host', + 'is_host': True + }, + } + + def test(self): + self.nodes[HOST].start(start_radvd=True, prefix=config.ONLINK_PREFIX, slaac=True) + self.simulator.go(5) + + self.nodes[BR1].start() + self.simulator.go(5) + self.assertEqual('leader', self.nodes[BR1].get_state()) + + self.nodes[ROUTER1].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[ROUTER1].get_state()) + + self.nodes[BR2].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[BR2].get_state()) + + self.simulator.go(10) + self.collect_ipaddrs() + + logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs()) + logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs()) + logging.info("BR2 addrs: %r", self.nodes[BR2].get_addrs()) + logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs()) + + self.assertGreaterEqual(len(self.nodes[HOST].get_addrs()), 2) + + self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[BR2].get_prefixes()) == 1) + + self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[BR2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1) + + # Router1 and BR2 can ping each other inside the Thread network. + self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[BR2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])) + self.assertTrue(self.nodes[BR2].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])) + + # Both Router1 and BR2 can ping to/from the Host on infra link. + self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) + self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0], + backbone=True)) + self.assertTrue(self.nodes[BR2].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) + self.assertTrue(self.nodes[HOST].ping(self.nodes[BR2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0], + backbone=True)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/border_routing/test_multi_thread_networks.py b/tests/scripts/thread-cert/border_routing/test_multi_thread_networks.py new file mode 100755 index 000000000..3e462a6ac --- /dev/null +++ b/tests/scripts/thread-cert/border_routing/test_multi_thread_networks.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +import logging +import unittest + +import config +import thread_cert + +# Test description: +# This test verifies bi-directional connectivity accross multiple Thread networks. +# +# Topology: +# -------------(eth)---------------- +# | | +# BR1 BR2 +# | | +# ED1 ED2 +# +# Thread Net1 Thread Net2 +# + +BR1 = 1 +ROUTER1 = 2 +BR2 = 3 +ROUTER2 = 4 + +CHANNEL1 = 18 +CHANNEL2 = 19 + + +class MultiThreadNetworks(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + + TOPOLOGY = { + BR1: { + 'name': 'BR_1', + 'allowlist': [ROUTER1], + 'is_otbr': True, + 'version': '1.2', + 'channel': CHANNEL1, + 'router_selection_jitter': 1, + }, + ROUTER1: { + 'name': 'Router_1', + 'allowlist': [BR1], + 'version': '1.2', + 'channel': CHANNEL1, + 'router_selection_jitter': 1, + }, + BR2: { + 'name': 'BR_2', + 'allowlist': [ROUTER2], + 'is_otbr': True, + 'version': '1.2', + 'channel': CHANNEL2, + 'router_selection_jitter': 1, + }, + ROUTER2: { + 'name': 'Router_2', + 'allowlist': [BR2], + 'version': '1.2', + 'channel': CHANNEL2, + 'router_selection_jitter': 1, + }, + } + + def test(self): + self.nodes[BR1].start() + self.simulator.go(5) + self.assertEqual('leader', self.nodes[BR1].get_state()) + + self.nodes[ROUTER1].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[ROUTER1].get_state()) + + self.nodes[BR2].start() + self.simulator.go(5) + self.assertEqual('leader', self.nodes[BR2].get_state()) + + self.nodes[ROUTER2].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[ROUTER2].get_state()) + + self.collect_ipaddrs() + + logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs()) + logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs()) + logging.info("BR2 addrs: %r", self.nodes[BR2].get_addrs()) + logging.info("ROUTER2 addrs: %r", self.nodes[ROUTER2].get_addrs()) + + self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[BR2].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[ROUTER2].get_prefixes()) == 1) + + self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + + self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])) + self.assertTrue(self.nodes[ROUTER2].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/border_routing/test_single_border_router.py b/tests/scripts/thread-cert/border_routing/test_single_border_router.py new file mode 100755 index 000000000..ee598479b --- /dev/null +++ b/tests/scripts/thread-cert/border_routing/test_single_border_router.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2020, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +import logging +import unittest + +import config +import thread_cert + +# Test description: +# This test verifies bi-directional connectivity between Thread end device +# and infra host. +# +# Topology: +# ----------------(eth)-------------------- +# | | +# BR1 (Leader) HOST +# | +# ROUTER1 +# + +BR1 = 1 +ROUTER1 = 2 +HOST = 4 + +CHANNEL1 = 18 + +# The two prefixes are set small enough that a random-generated OMR prefix is +# very likely greater than them. So that the duckhorn BR will remove the random-generated one. +ON_MESH_PREFIX1 = "fd00:00:00:01::/64" +ON_MESH_PREFIX2 = "fd00:00:00:02::/64" + + +class SingleBorderRouter(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + + TOPOLOGY = { + BR1: { + 'name': 'BR_1', + 'allowlist': [ROUTER1], + 'is_otbr': True, + 'version': '1.2', + 'channel': CHANNEL1, + 'router_selection_jitter': 2, + }, + ROUTER1: { + 'name': 'Router_1', + 'allowlist': [BR1], + 'version': '1.2', + 'channel': CHANNEL1, + 'router_selection_jitter': 2, + }, + HOST: { + 'name': 'Host', + 'is_host': True + }, + } + + def test(self): + self.nodes[HOST].start(start_radvd=False) + self.simulator.go(5) + + self.nodes[BR1].start() + self.simulator.go(5) + self.assertEqual('leader', self.nodes[BR1].get_state()) + + self.nodes[ROUTER1].start() + self.simulator.go(5) + self.assertEqual('router', self.nodes[ROUTER1].get_state()) + + self.simulator.go(10) + self.collect_ipaddrs() + + logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs()) + logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs()) + logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs()) + + self.assertGreaterEqual(len(self.nodes[HOST].get_addrs()), 2) + + self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1) + + self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1) + self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1) + + # Router1 can ping to/from the Host on infra link. + self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) + self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0], + backbone=True)) + + # Add two on-mesh prefix on BR1, so that + # it will deregister its random-generated OMR prefix. + self.nodes[BR1].add_prefix(ON_MESH_PREFIX1) + self.nodes[BR1].add_prefix(ON_MESH_PREFIX2) + self.nodes[BR1].register_netdata() + + self.simulator.go(10) + self.collect_ipaddrs() + + logging.info("BR1 addrs: %r", self.nodes[BR1].get_addrs()) + logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs()) + logging.info("HOST addrs: %r", self.nodes[HOST].get_addrs()) + + self.assertGreaterEqual(len(self.nodes[HOST].get_addrs()), 2) + + self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 2) + self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 2) + + self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 2) + self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 2) + self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1) + + # Router1 can ping to/from the Host on infra link. + self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])) + self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0], + backbone=True)) + self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[1], + backbone=True)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py index a1851c7ce..eaff213fe 100644 --- a/tests/scripts/thread-cert/config.py +++ b/tests/scripts/thread-cert/config.py @@ -78,6 +78,11 @@ ALL_NETWORK_BBRS_ADDRESS = 'ff32:40:fd00:db8:0:0:0:3' ALL_DOMAIN_BBRS_ADDRESS = 'ff32:40:fd00:7d03:7d03:7d03:0:3' ALL_DOMAIN_BBRS_ADDRESS_ALTER = 'ff32:40:fd00:7d04:7d04:7d04:0:3' +ONLINK_PREFIX = 'fd00:dead:face::/64' + +# Any address starts with 'fd' are considered on-link address. +ONLINK_PREFIX_REGEX_PATTERN = '^fd' + DEFAULT_MASTER_KEY = bytearray([ 0x00, 0x11, @@ -106,6 +111,8 @@ class ADDRESS_TYPE(Enum): ML_EID = 'ML_EID' DUA = 'DUA' BACKBONE_GUA = 'BACKBONE_GUA' + OMR = 'OMR' + ONLINK_ULA = 'ONLINK_ULA' RSSI = { diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index 221199b09..9385405b6 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -219,6 +219,7 @@ class OtbrDocker: def _setup_sysctl(self): self.bash(f'sysctl net.ipv6.conf.{self.ETH_DEV}.accept_ra=2') + self.bash(f'sysctl net.ipv6.conf.{self.ETH_DEV}.accept_ra_rt_info_max_plen=64') class OtCli: @@ -1169,6 +1170,17 @@ class NodeImpl: self.send_command(cmd) self._expect('Done') + def __getOmrAddress(self): + prefixes = [prefix.split('::')[0] for prefix in self.get_prefixes()] + omr_addrs = [] + for addr in self.get_addrs(): + for prefix in prefixes: + if (addr.startswith(prefix)): + omr_addrs.append(addr) + break + + return omr_addrs + def __getLinkLocalAddress(self): for ip6Addr in self.get_addrs(): if re.match(config.LINK_LOCAL_REGEX_PATTERN, ip6Addr, re.I): @@ -1242,6 +1254,8 @@ class NodeImpl: return self.__getDua() elif address_type == config.ADDRESS_TYPE.BACKBONE_GUA: return self._getBackboneGua() + elif address_type == config.ADDRESS_TYPE.OMR: + return self.__getOmrAddress() else: return None @@ -1264,6 +1278,21 @@ class NodeImpl: self.send_command(cmd) self._expect('Done') + def get_prefixes(self): + netdata = self.netdata_show() + prefixes = [] + + for i in range(1, len(netdata)): + if netdata[i].startswith("Routes:"): + break + prefixes.append(netdata[i]) + + return prefixes + + def netdata_show(self): + self.send_command('netdata show') + return self._expect_command_output('netdata show') + def add_route(self, prefix, stable=False, prf='med'): cmd = 'route add %s ' % prefix if stable: @@ -2046,12 +2075,22 @@ class LinuxHost(): return resp_count def _getBackboneGua(self) -> Optional[str]: - for ip6Addr in self.get_addrs(): - if re.match(config.BACKBONE_PREFIX_REGEX_PATTERN, ip6Addr, re.I): - return ip6Addr + for addr in self.get_addrs(): + if re.match(config.BACKBONE_PREFIX_REGEX_PATTERN, addr, re.I): + return addr return None + def _getInfraUla(self) -> Optional[str]: + """ Returns the ULA addresses autoconfigured on the infra link. + """ + addrs = [] + for addr in self.get_addrs(): + if re.match(config.ONLINK_PREFIX_REGEX_PATTERN, addr, re.I): + addrs.append(addr) + + return addrs + def ping(self, *args, **kwargs): backbone = kwargs.pop('backbone', False) if backbone: @@ -2099,9 +2138,12 @@ class HostNode(LinuxHost, OtbrDocker): self.name = name or ('Host%d' % nodeid) super().__init__(nodeid, **kwargs) - def start(self): + def start(self, start_radvd=True, prefix=config.DOMAIN_PREFIX, slaac=False): self._setup_sysctl() - self._service_radvd_start() + if start_radvd: + self._service_radvd_start(prefix, slaac) + else: + self._service_radvd_stop() def stop(self): self._service_radvd_stop() @@ -2121,14 +2163,16 @@ class HostNode(LinuxHost, OtbrDocker): Returns: IPv6 address string. """ - assert address_type == config.ADDRESS_TYPE.BACKBONE_GUA + assert address_type in [config.ADDRESS_TYPE.BACKBONE_GUA, config.ADDRESS_TYPE.ONLINK_ULA] if address_type == config.ADDRESS_TYPE.BACKBONE_GUA: return self._getBackboneGua() + if address_type == config.ADDRESS_TYPE.ONLINK_ULA: + return self._getInfraUla() else: return None - def _service_radvd_start(self): + def _service_radvd_start(self, prefix, slaac): self.bash("""cat >/etc/radvd.conf <