diff --git a/src/core/common/time_ticker.cpp b/src/core/common/time_ticker.cpp index c770275e5..2bfb130a1 100644 --- a/src/core/common/time_ticker.cpp +++ b/src/core/common/time_ticker.cpp @@ -94,6 +94,13 @@ void TimeTicker::HandleTimer(void) Get().HandleTimeTick(); } +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE + if (mReceivers & Mask(kNetworkDataNotifier)) + { + Get().HandleTimeTick(); + } +#endif + #if OPENTHREAD_CONFIG_CHILD_SUPERVISION_ENABLE if (mReceivers & Mask(kChildSupervisor)) { diff --git a/src/core/common/time_ticker.hpp b/src/core/common/time_ticker.hpp index 78860c4d6..a89f3e36e 100644 --- a/src/core/common/time_ticker.hpp +++ b/src/core/common/time_ticker.hpp @@ -71,6 +71,7 @@ public: kIp6FragmentReassembler, ///< `Ip6::Ip6` (handling of fragmented messages) kDuaManager, ///< `DuaManager` kMlrManager, ///< `MlrManager` + kNetworkDataNotifier, ///< `NetworkData::Notifier` kNumReceivers, ///< Number of receivers. }; diff --git a/src/core/config/border_router.h b/src/core/config/border_router.h index f3959d50d..ef5a3a1e3 100644 --- a/src/core/config/border_router.h +++ b/src/core/config/border_router.h @@ -55,6 +55,32 @@ #define OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE + * + * Define to 1 to enable mechanism on a Border Router which provides IP connectivity to request router role upgrade. + * + * This config is applicable on an `OPENTHREAD_FTD` build and when `OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE` is also + * enabled. + * + * A Border Router is considered to provide external IP connectivity if at least one of the below conditions hold: + * + * - It has added at least one external route entry. + * - It has added at least one prefix entry with default-route and on-mesh flags set. + * - It has added at least one domain prefix (domain and on-mesh flags set). + * + * A Border Router which provides IP connectivity and is acting as a REED is eligible to request a router role upgrade + * by sending an "Address Solicit" request to leader with status reason `BorderRouterRequest`. This reason is used when + * the number of active routers in the Thread mesh is above the threshold, and only if the number of existing eligible + * BRs (determined from the Thread Network Data) that are acting as router is less than two. This mechanism allows up + * to two eligible Border Routers to request router role upgrade when the number of routers is already above the + * threshold. + * + */ +#ifndef OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE +#define OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE 1 +#endif + /** * @def OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE * diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp index cc9e2b914..b18f21ba1 100644 --- a/src/core/thread/mle_router.cpp +++ b/src/core/thread/mle_router.cpp @@ -1453,7 +1453,11 @@ Error MleRouter::HandleAdvertisement(RxInfo &aRxInfo) if (routerCount > mRouterDowngradeThreshold && mRouterSelectionJitterTimeout == 0 && HasMinDowngradeNeighborRouters() && HasSmallNumberOfChildren() && - HasOneNeighborWithComparableConnectivity(route, routerId)) + HasOneNeighborWithComparableConnectivity(route, routerId) +#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE + && !Get().IsEligibleForRouterRoleUpgradeAsBorderRouter() +#endif + ) { mRouterSelectionJitterTimeout = 1 + Random::NonCrypto::GetUint8InRange(0, mRouterSelectionJitter); } @@ -3920,7 +3924,7 @@ void MleRouter::HandleAddressSolicit(Coap::Message &aMessage, const Ip6::Message case ThreadStatusTlv::kParentPartitionChange: break; - case ThreadStatusTlv::kBorderRouterRequst: + case ThreadStatusTlv::kBorderRouterRequest: if ((mRouterTable.GetActiveRouterCount() >= mRouterUpgradeThreshold) && (Get().CountBorderRouters(NetworkData::kRouterRoleOnly) >= kRouterUpgradeBorderRouterRequestThreshold)) diff --git a/src/core/thread/mle_router.hpp b/src/core/thread/mle_router.hpp index e84899f35..6493c512b 100644 --- a/src/core/thread/mle_router.hpp +++ b/src/core/thread/mle_router.hpp @@ -585,7 +585,7 @@ private: static constexpr uint16_t kUnsolicitedDataResponseJitter = 500; // Max delay for unsol Data Response (in msec). // Threshold to accept a router upgrade request with reason - // `kBorderRouterRequst` (number of BRs acting as router in + // `kBorderRouterRequest` (number of BRs acting as router in // Network Data). static constexpr uint8_t kRouterUpgradeBorderRouterRequestThreshold = 2; diff --git a/src/core/thread/mle_types.hpp b/src/core/thread/mle_types.hpp index ad987dbff..0dfdf9348 100644 --- a/src/core/thread/mle_types.hpp +++ b/src/core/thread/mle_types.hpp @@ -162,6 +162,13 @@ constexpr uint8_t kRouterSelectionJitter = 120; ///< (in sec) constexpr uint8_t kRouterDowngradeThreshold = 23; constexpr uint8_t kRouterUpgradeThreshold = 16; +/** + * Threshold to accept a router upgrade request with reason `kBorderRouterRequest` (number of BRs acting as router in + * Network Data). + * + */ +constexpr uint8_t kRouterUpgradeBorderRouterRequestThreshold = 2; + constexpr uint32_t kMaxLeaderToRouterTimeout = 90; ///< (in sec) constexpr uint32_t kReedAdvertiseInterval = 570; ///< (in sec) constexpr uint32_t kReedAdvertiseJitter = 60; ///< (in sec) diff --git a/src/core/thread/network_data.cpp b/src/core/thread/network_data.cpp index eccacff8e..3c96a9284 100644 --- a/src/core/thread/network_data.cpp +++ b/src/core/thread/network_data.cpp @@ -814,5 +814,34 @@ uint8_t NetworkData::CountBorderRouters(RoleFilter aRoleFilter) const return rlocsLength; } +bool NetworkData::ContainsBorderRouterWithRloc(uint16_t aRloc16) const +{ + bool contains = false; + Iterator iterator = kIteratorInit; + ExternalRouteConfig route; + OnMeshPrefixConfig prefix; + + while (GetNextExternalRoute(iterator, route) == kErrorNone) + { + if (route.mRloc16 == aRloc16) + { + ExitNow(contains = true); + } + } + + iterator = kIteratorInit; + + while (GetNextOnMeshPrefix(iterator, prefix) == kErrorNone) + { + if ((prefix.mRloc16 == aRloc16) && prefix.mOnMesh && (prefix.mDefaultRoute || prefix.mDp)) + { + ExitNow(contains = true); + } + } + +exit: + return contains; +} + } // namespace NetworkData } // namespace ot diff --git a/src/core/thread/network_data.hpp b/src/core/thread/network_data.hpp index 9272b755a..cbe513305 100644 --- a/src/core/thread/network_data.hpp +++ b/src/core/thread/network_data.hpp @@ -325,9 +325,9 @@ public: Error GetNextServer(Iterator &aIterator, uint16_t &aRloc16) const; /** - * This method finds and returns the list of RLOCs of border routers providing external IPv6 connectivity. + * This method finds and returns the list of RLOCs of border routers providing external IP connectivity. * - * A border router is considered to provide external IPv6 connectivity if it has added at least one external route + * A border router is considered to provide external IP connectivity if it has added at least one external route * entry, or an on-mesh prefix with default-route and on-mesh flags set. * * This method should be used when the RLOC16s are present in the Network Data (when the Network Data contains the @@ -346,10 +346,13 @@ public: Error FindBorderRouters(RoleFilter aRoleFilter, uint16_t aRlocs[], uint8_t &aRlocsLength) const; /** - * This method counts the number of border routers providing external IPv6 connectivity. + * This method counts the number of border routers providing external IP connectivity. * - * A border router is considered to provide external IPv6 connectivity if it has added at least one external route - * entry, or an on-mesh prefix with default-route and on-mesh flags set. + * A border router is considered to provide external IP connectivity if at least one of the below conditions hold + * + * - It has added at least one external route entry. + * - It has added at least one prefix entry with default-route and on-mesh flags set. + * - It has added at least one domain prefix (domain and on-mesh flags set). * * This method should be used when the RLOC16s are present in the Network Data (when the Network Data contains the * full set and not the stable subset). @@ -361,6 +364,27 @@ public: */ uint8_t CountBorderRouters(RoleFilter aRoleFilter) const; + /** + * This method indicates whether the network data contains a border providing external IP connectivity with a given + * RLOC16. + * + * A border router is considered to provide external IP connectivity if at least one of the below conditions hold + * + * - It has added at least one external route entry. + * - It has added at least one prefix entry with default-route and on-mesh flags set. + * - It has added at least one domain prefix (domain and on-mesh flags set). + * + * This method should be used when the RLOC16s are present in the Network Data (when the Network Data contains the + * full set and not the stable subset). + * + * @param[in] aRloc16 The RLOC16 to check. + * + * @returns TRUE If the network data contains a border router with @p aRloc16 providing IP connectivity. + * @returns FALSE If the network data does not contain a border router with @p aRloc16 providing IP connectivity. + * + */ + bool ContainsBorderRouterWithRloc(uint16_t aRloc16) const; + protected: /** * This enumeration defines Service Data match mode. diff --git a/src/core/thread/network_data_notifier.cpp b/src/core/thread/network_data_notifier.cpp index d8a53d4eb..00b15ce4c 100644 --- a/src/core/thread/network_data_notifier.cpp +++ b/src/core/thread/network_data_notifier.cpp @@ -38,23 +38,35 @@ #include "common/code_utils.hpp" #include "common/instance.hpp" #include "common/locator_getters.hpp" +#include "common/log.hpp" #include "thread/network_data_leader.hpp" #include "thread/network_data_local.hpp" namespace ot { namespace NetworkData { +RegisterLogModule("NetworkData"); + Notifier::Notifier(Instance &aInstance) : InstanceLocator(aInstance) , mTimer(aInstance, HandleTimer) , mSynchronizeDataTask(aInstance, HandleSynchronizeDataTask) , mNextDelay(0) , mWaitingForResponse(false) +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE + , mDidRequestRouterRoleUpgrade(false) + , mRouterRoleUpgradeTimeout(0) +#endif { } void Notifier::HandleServerDataUpdated(void) { +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE + mDidRequestRouterRoleUpgrade = false; + ScheduleRouterRoleUpgradeIfEligible(); +#endif + mNextDelay = 0; mSynchronizeDataTask.Post(); } @@ -113,6 +125,18 @@ void Notifier::HandleNotifierEvents(Events aEvents) mNextDelay = 0; } +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE + if (aEvents.Contains(kEventThreadPartitionIdChanged)) + { + mDidRequestRouterRoleUpgrade = false; + } + + if (aEvents.ContainsAny(kEventThreadRoleChanged | kEventThreadNetdataChanged | kEventThreadPartitionIdChanged)) + { + ScheduleRouterRoleUpgradeIfEligible(); + } +#endif + if (aEvents.ContainsAny(kEventThreadNetdataChanged | kEventThreadRoleChanged | kEventThreadChildRemoved)) { SynchronizeServerData(); @@ -158,6 +182,90 @@ void Notifier::HandleCoapResponse(Error aResult) } } +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE + +bool Notifier::IsEligibleForRouterRoleUpgradeAsBorderRouter(void) const +{ + bool isEligible = false; + uint16_t rloc16 = Get().GetRloc16(); + uint8_t activeRouterCount; + + VerifyOrExit(Get().IsRouterEligible()); + + // RouterUpgradeThreshold can be explicitly set to zero in some of + // cert tests to disallow device to become router. + + VerifyOrExit(Get().GetRouterUpgradeThreshold() != 0); + + // Check that we are a border router providing IP connectivity and already + // in the leader's network data and therefore eligible to request router + // role upgrade with `kBorderRouterRequest` status. + + VerifyOrExit(Get().ContainsBorderRouterWithRloc(rloc16) && + Get().ContainsBorderRouterWithRloc(rloc16)); + + activeRouterCount = Get().GetActiveRouterCount(); + VerifyOrExit((activeRouterCount >= Get().GetRouterUpgradeThreshold()) && + (activeRouterCount < Mle::kMaxRouters)); + + VerifyOrExit(Get().CountBorderRouters(kRouterRoleOnly) < Mle::kRouterUpgradeBorderRouterRequestThreshold); + isEligible = true; + +exit: + return isEligible; +} + +void Notifier::ScheduleRouterRoleUpgradeIfEligible(void) +{ + // We allow device to request router role upgrade using status + // reason `kBorderRouterRequest` once while its local network data + // remains unchanged. This ensures if the leader is running an + // older version of Thread stack which does not support + // `kBorderRouterRequest` reason, we do not keep trying (on no + // response). The boolean `mDidRequestRouterRoleUpgrade` tracks + // this. It is set to `false` when local network data gets changed + // or when partition ID gets changed (indicating a potential + // leader change). + + VerifyOrExit(!mDidRequestRouterRoleUpgrade); + + VerifyOrExit(Get().IsChild()); + VerifyOrExit(IsEligibleForRouterRoleUpgradeAsBorderRouter() && (mRouterRoleUpgradeTimeout == 0)); + + mRouterRoleUpgradeTimeout = Random::NonCrypto::GetUint8InRange(1, kRouterRoleUpgradeMaxTimeout + 1); + Get().RegisterReceiver(TimeTicker::kNetworkDataNotifier); + +exit: + return; +} + +void Notifier::HandleTimeTick(void) +{ + VerifyOrExit(mRouterRoleUpgradeTimeout > 0); + + mRouterRoleUpgradeTimeout--; + + if (mRouterRoleUpgradeTimeout == 0) + { + Get().UnregisterReceiver(TimeTicker::kNetworkDataNotifier); + + // Check that we are still eligible for requesting router role + // upgrade (note that state can change since the last time we + // checked and registered to receive time ticks). + + if (Get().IsChild() && IsEligibleForRouterRoleUpgradeAsBorderRouter()) + { + LogInfo("Requesting router role as BR"); + mDidRequestRouterRoleUpgrade = true; + IgnoreError(Get().BecomeRouter(ThreadStatusTlv::kBorderRouterRequest)); + } + } +exit: + return; +} +#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && + // OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE + } // namespace NetworkData } // namespace ot diff --git a/src/core/thread/network_data_notifier.hpp b/src/core/thread/network_data_notifier.hpp index b9d6d5efc..2388da739 100644 --- a/src/core/thread/network_data_notifier.hpp +++ b/src/core/thread/network_data_notifier.hpp @@ -43,6 +43,7 @@ #include "common/non_copyable.hpp" #include "common/notifier.hpp" #include "common/tasklet.hpp" +#include "common/time_ticker.hpp" namespace ot { namespace NetworkData { @@ -54,6 +55,7 @@ namespace NetworkData { class Notifier : public InstanceLocator, private NonCopyable { friend class ot::Notifier; + friend class ot::TimeTicker; public: /** @@ -74,10 +76,33 @@ public: */ void HandleServerDataUpdated(void); +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE + /** + * This method indicates whether the device as border router is eligible for router role upgrade request using the + * status reason `kBorderRouterRequest`. + * + * This method checks whether device is providing IP connectivity and that there are fewer than two border routers + * in network data acting as router. Device is considered to provide external IP connectivity if at least one of + * the below conditions hold: + * + * - It has added at least one external route entry. + * - It has added at least one prefix entry with default-route and on-mesh flags set. + * - It has added at least one domain prefix (domain and on-mesh flags set). + * + * This method does not check the current role of device. + * + * @retval TRUE Device is eligible to request router role upgrade as a border router. + * @retval FALSE Device is not eligible to request router role upgrade as a border router. + * + */ + bool IsEligibleForRouterRoleUpgradeAsBorderRouter(void) const; +#endif + private: - static constexpr uint32_t kDelayNoBufs = 1000; // in msec - static constexpr uint32_t kDelayRemoveStaleChildren = 5000; // in msec - static constexpr uint32_t kDelaySynchronizeServerData = 300000; // in msec + static constexpr uint32_t kDelayNoBufs = 1000; // in msec + static constexpr uint32_t kDelayRemoveStaleChildren = 5000; // in msec + static constexpr uint32_t kDelaySynchronizeServerData = 300000; // in msec + static constexpr uint8_t kRouterRoleUpgradeMaxTimeout = 10; // in sec void HandleNotifierEvents(Events aEvents); @@ -93,11 +118,20 @@ private: static void HandleSynchronizeDataTask(Tasklet &aTasklet); void SynchronizeServerData(void); +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE + void ScheduleRouterRoleUpgradeIfEligible(void); + void HandleTimeTick(void); +#endif TimerMilli mTimer; Tasklet mSynchronizeDataTask; uint32_t mNextDelay; - bool mWaitingForResponse; + bool mWaitingForResponse : 1; + +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTER_REQUEST_ROUTER_ROLE + bool mDidRequestRouterRoleUpgrade : 1; + uint8_t mRouterRoleUpgradeTimeout; +#endif }; } // namespace NetworkData diff --git a/src/core/thread/thread_tlvs.hpp b/src/core/thread/thread_tlvs.hpp index 24eb9193b..b9222743c 100644 --- a/src/core/thread/thread_tlvs.hpp +++ b/src/core/thread/thread_tlvs.hpp @@ -166,7 +166,7 @@ public: kTooFewRouters = 2, ///< Address Solicit due to too few routers. kHaveChildIdRequest = 3, ///< Address Solicit due to child ID request. kParentPartitionChange = 4, ///< Address Solicit due to parent partition change - kBorderRouterRequst = 5, ///< Address Solicit from Border Router request. + kBorderRouterRequest = 5, ///< Address Solicit from Border Router request. kUnrecognizedStatus = 6, ///< The requested status is unrecognized or not meaningful in a request. }; diff --git a/tests/scripts/thread-cert/Makefile.am b/tests/scripts/thread-cert/Makefile.am index 2604dc504..6af656aa6 100644 --- a/tests/scripts/thread-cert/Makefile.am +++ b/tests/scripts/thread-cert/Makefile.am @@ -153,6 +153,7 @@ EXTRA_DIST = \ sniffer_transport.py \ test_anycast.py \ test_anycast_locator.py \ + test_br_upgrade_router_role.py \ test_coap.py \ test_coap_block.py \ test_coap_observe.py \ @@ -232,6 +233,7 @@ check_PROGRAMS = \ check_SCRIPTS = \ test_anycast.py \ test_anycast_locator.py \ + test_br_upgrade_router_role.py \ test_coap.py \ test_coap_block.py \ test_coap_observe.py \ diff --git a/tests/scripts/thread-cert/test_br_upgrade_router_role.py b/tests/scripts/thread-cert/test_br_upgrade_router_role.py new file mode 100755 index 000000000..296be93b2 --- /dev/null +++ b/tests/scripts/thread-cert/test_br_upgrade_router_role.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2022, 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 ipaddress +import unittest + +import command +import thread_cert + +# Test description: +# +# This test verifies behavior of Border Routers (which provide IP connectivity) requesting router role within Thread +# mesh. + +# Topology: +# +# 5 FTD nodes, all connected. +# +# Topology is expected to change during execution of test. +# + +LEADER = 1 +ROUTER = 2 +BR1 = 3 +BR2 = 4 +BR3 = 5 + + +class BrUpgradeRouterRole(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + SUPPORT_NCP = False + + TOPOLOGY = { + LEADER: { + 'name': 'leader', + 'mode': 'rdn', + }, + ROUTER: { + 'name': 'reader', + 'mode': 'rdn', + }, + BR1: { + 'name': 'br-1', + 'mode': 'rdn', + }, + BR2: { + 'name': 'br-2', + 'mode': 'rdn', + }, + BR3: { + 'name': 'br-3', + 'mode': 'rdn', + }, + } + + def test(self): + leader = self.nodes[LEADER] + router = self.nodes[ROUTER] + br1 = self.nodes[BR1] + br2 = self.nodes[BR2] + br3 = self.nodes[BR3] + + #------------------------------------------------------------------------------------- + # Set the router upgrade threshold to 2 on all nodes. + + for node in [leader, router, br1, br2, br3]: + node.set_router_upgrade_threshold(2) + + #------------------------------------------------------------------------------------- + # Start the leader and router. + + leader.start() + self.simulator.go(10) + self.assertEqual(leader.get_state(), 'leader') + + router.start() + self.simulator.go(125) + self.assertEqual(router.get_state(), 'router') + + #------------------------------------------------------------------------------------- + # Start all three BRs, we expect all to stay as child since there are already two + # routers in the Thread mesh. + + br1.start() + br2.start() + br3.start() + self.simulator.go(125) + self.assertEqual(br1.get_state(), 'child') + self.assertEqual(br2.get_state(), 'child') + self.assertEqual(br3.get_state(), 'child') + + #------------------------------------------------------------------------------------- + # Add an external route on `br1`, it should now try to become a router requesting + # with status BorderRouterRequest. Verify that leader allows it to become router. + + br1.add_route('2001:dead:beef:cafe::/64', stable=True) + br1.register_netdata() + self.simulator.go(15) + self.assertEqual(br1.get_state(), 'router') + + #------------------------------------------------------------------------------------- + # Add a prefix with default route on `br2`, it should also become a router. + + br2.add_prefix('2001:dead:beef:2222::/64', 'paros') + br2.register_netdata() + self.simulator.go(15) + self.assertEqual(br2.get_state(), 'router') + + #------------------------------------------------------------------------------------- + # Add an external route on `br3`, it should not become a router since we already have + # two that requested router role upgrade as border router reason. + + br3.add_route('2001:dead:beef:cafe::/64', stable=True) + br3.register_netdata() + self.simulator.go(120) + self.assertEqual(br3.get_state(), 'child') + + #------------------------------------------------------------------------------------- + # Remove the external route on `br1`. This should now trigger `br3` to request a + # router role upgrade since number of BRs acting as router in network data is now + # below the threshold of two. + + br1.remove_route('2001:dead:beef:cafe::/64') + br1.register_netdata() + self.simulator.go(15) + self.assertEqual(br1.get_state(), 'router') + self.assertEqual(br3.get_state(), 'router') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/test_network_data.cpp b/tests/unit/test_network_data.cpp index 65499c0a2..44df23646 100644 --- a/tests/unit/test_network_data.cpp +++ b/tests/unit/test_network_data.cpp @@ -149,7 +149,8 @@ void TestNetworkDataIterator(void) }, }; - const uint16_t kRlocs[] = {0xc800, 0x5400}; + const uint16_t kRlocs[] = {0xc800, 0x5400}; + const uint16_t kNonExistingRlocs[] = {0xc700, 0x0000, 0x5401}; NetworkData netData(*instance, kNetworkData, sizeof(kNetworkData)); @@ -179,6 +180,16 @@ void TestNetworkDataIterator(void) SuccessOrQuit(netData.FindBorderRouters(kChildRoleOnly, rlocs, rlocsLength)); VerifyOrQuit(rlocsLength == 0); VerifyOrQuit(netData.CountBorderRouters(kChildRoleOnly) == 0); + + for (uint16_t rloc16 : kRlocs) + { + VerifyOrQuit(netData.ContainsBorderRouterWithRloc(rloc16)); + } + + for (uint16_t rloc16 : kNonExistingRlocs) + { + VerifyOrQuit(!netData.ContainsBorderRouterWithRloc(rloc16)); + } } { @@ -252,9 +263,10 @@ void TestNetworkDataIterator(void) }, }; - const uint16_t kRlocsAnyRole[] = {0x1000, 0x5400, 0x0401}; - const uint16_t kRlocsRouterRole[] = {0x1000, 0x5400}; - const uint16_t kRlocsChildRole[] = {0x0401}; + const uint16_t kRlocsAnyRole[] = {0x1000, 0x5400, 0x0401}; + const uint16_t kRlocsRouterRole[] = {0x1000, 0x5400}; + const uint16_t kRlocsChildRole[] = {0x0401}; + const uint16_t kNonExistingRlocs[] = {0x6000, 0x0000, 0x0402}; NetworkData netData(*instance, kNetworkData, sizeof(kNetworkData)); @@ -297,6 +309,16 @@ void TestNetworkDataIterator(void) rlocsLength = GetArrayLength(kRlocsAnyRole); SuccessOrQuit(netData.FindBorderRouters(kAnyRole, rlocs, rlocsLength)); VerifyRlocsArray(rlocs, rlocsLength, kRlocsAnyRole); + + for (uint16_t rloc16 : kRlocsAnyRole) + { + VerifyOrQuit(netData.ContainsBorderRouterWithRloc(rloc16)); + } + + for (uint16_t rloc16 : kNonExistingRlocs) + { + VerifyOrQuit(!netData.ContainsBorderRouterWithRloc(rloc16)); + } } { @@ -410,9 +432,10 @@ void TestNetworkDataIterator(void) }, }; - const uint16_t kRlocsAnyRole[] = {0xec00, 0x2801, 0x2800}; - const uint16_t kRlocsRouterRole[] = {0xec00, 0x2800}; - const uint16_t kRlocsChildRole[] = {0x2801}; + const uint16_t kRlocsAnyRole[] = {0xec00, 0x2801, 0x2800}; + const uint16_t kRlocsRouterRole[] = {0xec00, 0x2800}; + const uint16_t kRlocsChildRole[] = {0x2801}; + const uint16_t kNonExistingRlocs[] = {0x6000, 0x0000, 0x2806, 0x4c00}; NetworkData netData(*instance, kNetworkData, sizeof(kNetworkData)); @@ -451,6 +474,16 @@ void TestNetworkDataIterator(void) SuccessOrQuit(netData.FindBorderRouters(kChildRoleOnly, rlocs, rlocsLength)); VerifyRlocsArray(rlocs, rlocsLength, kRlocsChildRole); VerifyOrQuit(netData.CountBorderRouters(kChildRoleOnly) == GetArrayLength(kRlocsChildRole)); + + for (uint16_t rloc16 : kRlocsAnyRole) + { + VerifyOrQuit(netData.ContainsBorderRouterWithRloc(rloc16)); + } + + for (uint16_t rloc16 : kNonExistingRlocs) + { + VerifyOrQuit(!netData.ContainsBorderRouterWithRloc(rloc16)); + } } testFreeInstance(instance);