From f127469f7dd3e95833572b4628a20f35cb086504 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Tue, 20 Aug 2024 08:29:33 -0700 Subject: [PATCH] [routing-manager] check reachability & send ICMPv6 unreach err (#10548) This commit introduces a mechanism to check for reachability of messages forwarded by the BR and send an ICMPv6 Destination Unreachable error to the sender if needed. Specifically, if the Border Router (BR) decides to forward an IPv6 message outside the AIL and the message's source address matches a BR-generated ULA OMR prefix (with low preference), and the destination is unreachable using this source address, then an ICMPv6 Destination Unreachable message is sent back to the sender. For example, this situation can occur when a local, non-infrastructure-derived ULA OMR prefix is published alongside a `::/0` route (due to discovered PIO/RIO prefixes by the BR). A Thread mesh device may try to reach addresses beyond the local AIL (e.g., the global internet) using the ULA OMR prefix, which would be unreachable. This feature is controlled by an OT config flag, enabled by default. Alternatively, this functionality may be implemented within the platform layer, in which case the configuration should be disabled. This commit also adds a test case `test-504-br-icmp-unreach-err.py` validating the newly added behavior. --- include/openthread/border_routing.h | 12 ++ include/openthread/icmp6.h | 5 +- include/openthread/instance.h | 2 +- src/cli/cli_br.cpp | 15 +- src/core/api/border_routing_api.cpp | 10 ++ src/core/border_router/routing_manager.cpp | 98 ++++++++++ src/core/border_router/routing_manager.hpp | 34 ++++ src/core/config/border_routing.h | 37 ++++ src/core/net/icmp6.hpp | 5 +- src/core/net/ip6.cpp | 7 + src/core/thread/network_data_leader.cpp | 18 ++ src/core/thread/network_data_leader.hpp | 11 ++ tests/toranj/cli/cli.py | 3 + .../cli/test-504-br-icmp-unreach-err.py | 167 ++++++++++++++++++ tests/toranj/openthread-core-toranj-config.h | 2 + tests/toranj/start.sh | 2 + 16 files changed, 422 insertions(+), 6 deletions(-) create mode 100755 tests/toranj/cli/test-504-br-icmp-unreach-err.py diff --git a/include/openthread/border_routing.h b/include/openthread/border_routing.h index d45903249..272005160 100644 --- a/include/openthread/border_routing.h +++ b/include/openthread/border_routing.h @@ -598,6 +598,18 @@ void otBorderRoutingDhcp6PdSetRequestCallback(otInstance otBorderRoutingRequestDhcp6PdCallback aCallback, void *aContext); +/** + * Sets the local on-link prefix. + * + * Requires `OPENTHREAD_CONFIG_BORDER_ROUTING_TESTING_API_ENABLE`. + * + * This is intended for testing only and using it will make the BR non-compliant with the Thread Specification. + * + * @param[in] aPrefix The on-link prefix to use. + * + */ +void otBorderRoutingSetOnLinkPrefix(otInstance *aInstance, const otIp6Prefix *aPrefix); + /** * @} * diff --git a/include/openthread/icmp6.h b/include/openthread/icmp6.h index 925871cda..e7359ac29 100644 --- a/include/openthread/icmp6.h +++ b/include/openthread/icmp6.h @@ -76,8 +76,9 @@ typedef enum otIcmp6Type */ typedef enum otIcmp6Code { - OT_ICMP6_CODE_DST_UNREACH_NO_ROUTE = 0, ///< Destination Unreachable No Route - OT_ICMP6_CODE_FRAGM_REAS_TIME_EX = 1, ///< Fragment Reassembly Time Exceeded + OT_ICMP6_CODE_DST_UNREACH_NO_ROUTE = 0, ///< Destination Unreachable (Type 1) - No Route + OT_ICMP6_CODE_DST_UNREACH_PROHIBITED = 1, ///< Destination Unreachable (Type 1) - Administratively Prohibited + OT_ICMP6_CODE_FRAGM_REAS_TIME_EX = 1, ///< Time Exceeded (Type 3) - Fragment Reassembly } otIcmp6Code; #define OT_ICMP6_HEADER_DATA_SIZE 4 ///< Size of ICMPv6 Header. diff --git a/include/openthread/instance.h b/include/openthread/instance.h index f5c12bbf1..891398807 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 (433) +#define OPENTHREAD_API_VERSION (434) /** * @addtogroup api-instance diff --git a/src/cli/cli_br.cpp b/src/cli/cli_br.cpp index b49c774ce..98a5be48c 100644 --- a/src/cli/cli_br.cpp +++ b/src/cli/cli_br.cpp @@ -258,7 +258,20 @@ template <> otError Br::Process(Arg aArgs[]) otError error = OT_ERROR_NONE; PrefixType outputPrefixTypes; - SuccessOrExit(error = ParsePrefixTypeArgs(aArgs, outputPrefixTypes)); +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TESTING_API_ENABLE + if (aArgs[0] == "test") + { + otIp6Prefix prefix; + + SuccessOrExit(error = aArgs[1].ParseAsIp6Prefix(prefix)); + otBorderRoutingSetOnLinkPrefix(GetInstancePtr(), &prefix); + ExitNow(); + } +#endif + + error = ParsePrefixTypeArgs(aArgs, outputPrefixTypes); + + SuccessOrExit(error); /** * @cli br onlinkprefix local diff --git a/src/core/api/border_routing_api.cpp b/src/core/api/border_routing_api.cpp index a7e477f8c..bb110cac8 100644 --- a/src/core/api/border_routing_api.cpp +++ b/src/core/api/border_routing_api.cpp @@ -217,6 +217,7 @@ uint16_t otBorderRoutingCountPeerBrs(otInstance *aInstance, uint32_t *aMinAge) #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + void otBorderRoutingDhcp6PdSetEnabled(otInstance *aInstance, bool aEnabled) { AsCoreType(aInstance).Get().SetDhcp6PdEnabled(aEnabled); @@ -237,4 +238,13 @@ void otBorderRoutingDhcp6PdSetRequestCallback(otInstance #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TESTING_API_ENABLE + +void otBorderRoutingSetOnLinkPrefix(otInstance *aInstance, const otIp6Prefix *aPrefix) +{ + AsCoreType(aInstance).Get().SetOnLinkPrefix(AsCoreType(aPrefix)); +} + +#endif + #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index b50dd44b2..cd7c4c19f 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -817,6 +817,62 @@ bool RoutingManager::NetworkDataContainsUlaRoute(void) const return contains; } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_REACHABILITY_CHECK_ICMP6_ERROR_ENABLE + +void RoutingManager::CheckReachabilityToSendIcmpError(const Message &aMessage, const Ip6::Header &aIp6Header) +{ + bool matchesUlaOmrLowPrf = false; + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::OnMeshPrefixConfig prefixConfig; + Ip6::MessageInfo messageInfo; + + VerifyOrExit(IsRunning() && IsInitialPolicyEvaluationDone()); + + VerifyOrExit(!aIp6Header.GetDestination().IsMulticast()); + + // Validate that source matches a ULA OMR prefix with low preference + // (indicating it is not infrastructure-derived). + + while (Get().GetNextOnMeshPrefix(iterator, prefixConfig) == kErrorNone) + { + if (IsValidOmrPrefix(prefixConfig) && prefixConfig.GetPrefix().IsUniqueLocal() && + aIp6Header.GetSource().MatchesPrefix(prefixConfig.GetPrefix())) + { + if (prefixConfig.GetPreference() >= NetworkData::kRoutePreferenceMedium) + { + matchesUlaOmrLowPrf = false; + break; + } + + matchesUlaOmrLowPrf = true; + + // Keep checking other prefixes, as the same prefix might + // be added with a higher preference by another BR. + } + } + + VerifyOrExit(matchesUlaOmrLowPrf); + + VerifyOrExit(!mRxRaTracker.IsAddressOnLink(aIp6Header.GetDestination())); + VerifyOrExit(!mRxRaTracker.IsAddressReachableThroughExplicitRoute(aIp6Header.GetDestination())); + VerifyOrExit(!Get().IsNat64(aIp6Header.GetDestination())); + + LogInfo("Send ICMP unreachable for fwd msg with local ULA src and non-local dst"); + LogInfo(" src: %s", aIp6Header.GetSource().ToString().AsCString()); + LogInfo(" dst: %s", aIp6Header.GetDestination().ToString().AsCString()); + + messageInfo.Clear(); + messageInfo.SetPeerAddr(aIp6Header.GetSource()); + + IgnoreError(Get().SendError(Ip6::Icmp::Header::kTypeDstUnreach, + Ip6::Icmp::Header::kCodeDstUnreachProhibited, messageInfo, aMessage)); + +exit: + return; +} + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_REACHABILITY_CHECK_ICMP6_ERROR_ENABLE + #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) void RoutingManager::LogPrefixInfoOption(const Ip6::Prefix &aPrefix, @@ -1804,6 +1860,48 @@ void RoutingManager::RxRaTracker::SetHeaderFlagsOn(RouterAdvert::Header &aHeader } } +bool RoutingManager::RxRaTracker::IsAddressOnLink(const Ip6::Address &aAddress) const +{ + bool isOnLink = false; + + for (const Router &router : mRouters) + { + for (const OnLinkPrefix &onLinkPrefix : router.mOnLinkPrefixes) + { + isOnLink = aAddress.MatchesPrefix(onLinkPrefix.GetPrefix()); + VerifyOrExit(!isOnLink); + } + } + +exit: + return isOnLink; +} + +bool RoutingManager::RxRaTracker::IsAddressReachableThroughExplicitRoute(const Ip6::Address &aAddress) const +{ + // Checks whether the `aAddress` matches any discovered route + // prefix excluding `::/0`. + + bool isReachable = false; + + for (const Router &router : mRouters) + { + for (const RoutePrefix &routePrefix : router.mRoutePrefixes) + { + if (routePrefix.GetPrefix().GetLength() == 0) + { + continue; + } + + isReachable = aAddress.MatchesPrefix(routePrefix.GetPrefix()); + VerifyOrExit(!isReachable); + } + } + +exit: + return isReachable; +} + void RoutingManager::RxRaTracker::InitIterator(PrefixTableIterator &aIterator) const { static_cast(aIterator).Init(mRouters.GetHead(), Uptime::MsecToSec(Get().GetUptime())); diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index 95dc4c0ec..6aa622abb 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -602,6 +602,34 @@ public: #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE +#if OPENTHREAD_CONFIG_BORDER_ROUTING_REACHABILITY_CHECK_ICMP6_ERROR_ENABLE + /** + * Determines whether to send an ICMPv6 Destination Unreachable error to the sender based on reachability and + * source address. + * + * Specifically, if the Border Router (BR) decides to forward a unicast IPv6 message outside the AIL and the + * message's source address matches a BR-generated ULA OMR prefix (with low preference), and the destination is + * unreachable using this source address, then an ICMPv6 Destination Unreachable message is sent back to the sender. + * + * @param[in] aMessage The message. + * @param[in] aIp6Header The IPv6 header of @p aMessage. + * + */ + void CheckReachabilityToSendIcmpError(const Message &aMessage, const Ip6::Header &aIp6Header); +#endif + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TESTING_API_ENABLE + /** + * Sets the local on-link prefix. + * + * This is intended for testing only and using it will make a device non-compliant with the Thread Specification. + * + * @param[in] aPrefix The on-link prefix to use. + * + */ + void SetOnLinkPrefix(const Ip6::Prefix &aPrefix) { mOnLinkPrefixManager.SetLocalPrefix(aPrefix); } +#endif + private: //------------------------------------------------------------------------------------------------------------------ // Constants @@ -859,6 +887,9 @@ private: const RouterAdvert::Header &GetLocalRaHeaderToMirror(void) const { return mLocalRaHeader; } + bool IsAddressOnLink(const Ip6::Address &aAddress) const; + bool IsAddressReachableThroughExplicitRoute(const Ip6::Address &aAddress) const; + // Iterating over discovered items void InitIterator(PrefixTableIterator &aIterator) const; Error GetNextEntry(PrefixTableIterator &aIterator, PrefixTableEntry &aEntry) const; @@ -1178,6 +1209,9 @@ private: void HandleNetDataChange(void); void HandleExtPanIdChange(void); void HandleTimer(void); +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TESTING_API_ENABLE + void SetLocalPrefix(const Ip6::Prefix &aPrefix) { mLocalPrefix = aPrefix; } +#endif private: enum State : uint8_t // State of `mLocalPrefix` diff --git a/src/core/config/border_routing.h b/src/core/config/border_routing.h index 7a91d6a39..652387800 100644 --- a/src/core/config/border_routing.h +++ b/src/core/config/border_routing.h @@ -88,6 +88,31 @@ #define OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE #endif +/** + * @def OPENTHREAD_CONFIG_BORDER_ROUTING_REACHABILITY_CHECK_ICMP6_ERROR_ENABLE + * + * Define to 1 to allow Routing Manager to check for reachability of messages being forwarded by the BR and determine + * whether to send an ICMPv6 Destination Unreachable error back to the sender. + * + * Specifically, if the Border Router (BR) decides to forward a unicast IPv6 message outside the AIL and the message's + * source address matches a BR-generated ULA OMR prefix (with low preference), and the destination is unreachable + * using this source address, then an ICMPv6 Destination Unreachable message is sent back to the sender. + * + * For example, this situation can occur when a local, non-infrastructure-derived ULA OMR prefix is published alongside + * a `::/0` route (due to discovered PIO/RIO prefixes by the BR). A Thread mesh device may try to reach addresses + * beyond the local AIL (e.g., the global internet) using its ULA OMR address as source, which would be unreachable. + * + * Alternatively, this functionality may be implemented within the platform layer, in which case this configuration + * should be disabled. Note that the platform layer is always responsible for implementing generation of "ICMPv6 + * Destination Unreachable - No Route" messages. This reachability function will only generate "ICMPv6 Destination + * Unreachable - Communication Administratively Prohibited" messages for specific cases where there may be a + * default route to the destination but the source address type prohibits usable communication with this destination. + * + */ +#ifndef OPENTHREAD_CONFIG_BORDER_ROUTING_REACHABILITY_CHECK_ICMP6_ERROR_ENABLE +#define OPENTHREAD_CONFIG_BORDER_ROUTING_REACHABILITY_CHECK_ICMP6_ERROR_ENABLE 1 +#endif + /** * @def OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_DISCOVERED_ROUTERS * @@ -177,6 +202,18 @@ #define OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_BORDER_ROUTING_TESTING_API_ENABLE + * + * Define to 1 to enable testing related APIs to be provided by the `RoutingManager`. + * + * This is intended for testing only. Production devices SHOULD set this to zero. + * + */ +#ifndef OPENTHREAD_CONFIG_BORDER_ROUTING_TESTING_API_ENABLE +#define OPENTHREAD_CONFIG_BORDER_ROUTING_TESTING_API_ENABLE 0 +#endif + /** * @def OPENTHREAD_CONFIG_BORDER_ROUTING_MOCK_PLAT_APIS_ENABLE * diff --git a/src/core/net/icmp6.hpp b/src/core/net/icmp6.hpp index 48dc6ccf9..8ea9df0db 100644 --- a/src/core/net/icmp6.hpp +++ b/src/core/net/icmp6.hpp @@ -100,8 +100,9 @@ public: */ enum Code : uint8_t { - kCodeDstUnreachNoRoute = OT_ICMP6_CODE_DST_UNREACH_NO_ROUTE, ///< Destination Unreachable No Route - kCodeFragmReasTimeEx = OT_ICMP6_CODE_FRAGM_REAS_TIME_EX, ///< Fragment Reassembly Time Exceeded + kCodeDstUnreachNoRoute = OT_ICMP6_CODE_DST_UNREACH_NO_ROUTE, ///< Dest Unreachable - No Route + kCodeDstUnreachProhibited = OT_ICMP6_CODE_DST_UNREACH_PROHIBITED, ///< Dest Unreachable - Admin Prohibited + kCodeFragmReasTimeEx = OT_ICMP6_CODE_FRAGM_REAS_TIME_EX, ///< Time Exceeded - Frag Reassembly }; static constexpr uint8_t kTypeFieldOffset = 0; ///< The byte offset of Type field in ICMP6 header. diff --git a/src/core/net/ip6.cpp b/src/core/net/ip6.cpp index 2c63841f9..6ca08a024 100644 --- a/src/core/net/ip6.cpp +++ b/src/core/net/ip6.cpp @@ -958,6 +958,13 @@ Error Ip6::PassToHost(OwnedPtr &aMessagePtr, // Do not pass IPv6 packets that exceed kMinimalMtu. VerifyOrExit(aMessagePtr->GetLength() <= kMinimalMtu, error = kErrorDrop); +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTING_REACHABILITY_CHECK_ICMP6_ERROR_ENABLE + if (!aReceive) + { + Get().CheckReachabilityToSendIcmpError(*aMessagePtr, aHeader); + } +#endif + // If the sender used mesh-local address as source, do not pass to // host unless this message is intended for this device itself. if (Get().IsMeshLocalAddress(aHeader.GetSource())) diff --git a/src/core/thread/network_data_leader.cpp b/src/core/thread/network_data_leader.cpp index d6742a923..ead2a95a7 100644 --- a/src/core/thread/network_data_leader.cpp +++ b/src/core/thread/network_data_leader.cpp @@ -132,6 +132,24 @@ Error Leader::GetPreferredNat64Prefix(ExternalRouteConfig &aConfig) const return error; } +bool Leader::IsNat64(const Ip6::Address &aAddress) const +{ + bool isNat64 = false; + Iterator iterator = kIteratorInit; + ExternalRouteConfig config; + + while (GetNextExternalRoute(iterator, config) == kErrorNone) + { + if (config.mNat64 && config.GetPrefix().IsValidNat64() && aAddress.MatchesPrefix(config.GetPrefix())) + { + isNat64 = true; + break; + } + } + + return isNat64; +} + const PrefixTlv *Leader::FindNextMatchingPrefixTlv(const Ip6::Address &aAddress, const PrefixTlv *aPrevTlv) const { // This method iterates over Prefix TLVs which match a given IPv6 diff --git a/src/core/thread/network_data_leader.hpp b/src/core/thread/network_data_leader.hpp index 22566d531..4cb109eef 100644 --- a/src/core/thread/network_data_leader.hpp +++ b/src/core/thread/network_data_leader.hpp @@ -327,6 +327,17 @@ public: */ Error GetPreferredNat64Prefix(ExternalRouteConfig &aConfig) const; + /** + * Indicates whether or not the given IPv6 address matches any NAT64 prefixes. + * + * @param[in] aAddress An IPv6 address to check. + * + * @retval TRUE If @p aAddress matches a NAT64 prefix. + * @retval FALSE If @p aAddress does not match a NAT64 prefix. + * + */ + bool IsNat64(const Ip6::Address &aAddress) const; + #if OPENTHREAD_FTD /** * Defines the match mode constants to compare two RLOC16 values. diff --git a/tests/toranj/cli/cli.py b/tests/toranj/cli/cli.py index 06f7bd3c9..fef3be27e 100644 --- a/tests/toranj/cli/cli.py +++ b/tests/toranj/cli/cli.py @@ -802,6 +802,9 @@ class Node(object): def br_get_local_onlinkprefix(self): return self._cli_single_output('br onlinkprefix local') + def br_set_test_local_onlinkprefix(self, prefix): + self._cli_no_output('br onlinkprefix test', prefix) + def br_get_routeprf(self): return self._cli_single_output('br routeprf') diff --git a/tests/toranj/cli/test-504-br-icmp-unreach-err.py b/tests/toranj/cli/test-504-br-icmp-unreach-err.py new file mode 100755 index 000000000..9cb846399 --- /dev/null +++ b/tests/toranj/cli/test-504-br-icmp-unreach-err.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2024, 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. + +from cli import verify +from cli import verify_within +import cli +import time + +# ----------------------------------------------------------------------------------------------------------------------- +# Test description: +# +# BR sending ICMPv6 Unreachable error. +# +# - - - AIL- - - +# | | +# BR1 BR2 +# | +# router2 +# + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print('-' * 120) +print('Starting \'{}\''.format(test_name)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Creating `cli.Nodes` instances + +speedup = 20 +cli.Node.set_time_speedup_factor(speedup) + +br1 = cli.Node() +br2 = cli.Node() +router2 = cli.Node() + +IF_INDEX = 1 + +# ----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +# Start `br1` on its own Thread network with a custom OMR +# prefix and a custom on-link prefix. Both prefixes are +# non-ULA. + +br1.form('net1') +verify(br1.get_state() == 'leader') + +br1.add_prefix('1000::/64', 'paors') +br1.register_netdata() + +br1.br_init(IF_INDEX, 1) +br1.br_enable() + +br1.br_set_test_local_onlinkprefix('2000::/64') + +time.sleep(2) +verify(br1.br_get_state() == 'running') + +br1_local_onlink = br1.br_get_local_onlinkprefix() +br1_favored_onlink = br1.br_get_favored_onlinkprefix().split()[0] +verify(br1_local_onlink == br1_favored_onlink) +verify(br1_local_onlink == '2000:0:0:0::/64') + +br1_local_omr = br1.br_get_local_omrprefix() +br1_favored_omr = br1.br_get_favored_omrprefix().split()[0] +verify(br1_favored_omr == '1000:0:0:0::/64') + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Start `br2` on its own Thread network. + +br2.form('net2') +router2.join(br2) +verify(br2.get_state() == 'leader') +verify(router2.get_state() == 'router') + +br2.br_init(IF_INDEX, 1) +br2.br_enable() + +time.sleep(2) +verify(br2.br_get_state() == 'running') + +br2_local_omr = br2.br_get_local_omrprefix() +br2_favored_omr = br2.br_get_favored_omrprefix().split()[0] +verify(br2_local_omr == br2_favored_omr) + +br2_local_onlink = br2.br_get_local_onlinkprefix() +br2_favored_onlink = br2.br_get_favored_onlinkprefix().split()[0] +verify(br2_local_onlink != br2_favored_onlink) +verify(br2_favored_onlink == '2000:0:0:0::/64') + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Validate that the `br2` published '::/0' route due to +# discovery of a non-ULA on-link prefix. + +for node in [br2, router2]: + routes = node.get_netdata_routes() + verify(len(routes) == 1) + verify(routes[0].startswith('::/0 s')) + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Send a ping from `router2` to an AIL address (derived from the +# advertised on-link prefix). Verify that, BR2 receives and +# forwards the message, and BR2 does not send an ICMPv6 error, as +# the destination is within the AIL. + +router2.ping('2000::1', verify_success=False) + +verify(br2.get_br_counter_unicast_outbound_packets() == 1) + +rx_history = router2.cli('history rx list') +verify(len(rx_history) == 0) + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Send a ping from `router2` to an address on BR1 mesh network +# (derived from its OMR prefix). Verify that again it is +# received and forwarded by BR2 and again no ICMPv6 error is +# sent. + +router2.ping('1000::1', verify_success=False) + +verify(br2.get_br_counter_unicast_outbound_packets() == 2) + +rx_history = router2.cli('history rx list') +verify(len(rx_history) == 0) + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Send a ping to an outside AIL address, make sure BR2 +# does receive and forward it and now also sends an ICMPv6 +# error + +router2.ping('3000::', verify_success=False) + +verify(br2.get_br_counter_unicast_outbound_packets() == 3) + +rx_history = router2.cli('history rx list') +verify(rx_history[1].strip().startswith('type:ICMP6(Unreach)')) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test finished + +cli.Node.finalize_all_nodes() + +print('\'{}\' passed.'.format(test_name)) diff --git a/tests/toranj/openthread-core-toranj-config.h b/tests/toranj/openthread-core-toranj-config.h index 9f6b66a8d..8e6bbebe9 100644 --- a/tests/toranj/openthread-core-toranj-config.h +++ b/tests/toranj/openthread-core-toranj-config.h @@ -57,6 +57,8 @@ #define OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE 1 +#define OPENTHREAD_CONFIG_BORDER_ROUTING_TESTING_API_ENABLE 1 + #define OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE 1 #define OPENTHREAD_CONFIG_TMF_NETDIAG_CLIENT_ENABLE 1 diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index d242752b9..482774c85 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -202,6 +202,8 @@ if [ "$TORANJ_CLI" = 1 ]; then run cli/test-500-two-brs-two-networks.py run cli/test-501-multi-br-failure-recovery.py run cli/test-502-multi-br-leader-failure-recovery.py + run cli/test-503-peer-tbr-discovery.py + run cli/test-504-br-icmp-unreach-err.py run cli/test-601-channel-manager-channel-change.py # Skip the "channel-select" test on a TREL only radio link, since it # requires energy scan which is not supported in this case.