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.