diff --git a/include/openthread/border_routing.h b/include/openthread/border_routing.h index 19923aa6b..d45903249 100644 --- a/include/openthread/border_routing.h +++ b/include/openthread/border_routing.h @@ -131,6 +131,16 @@ typedef struct otBorderRoutingPrefixTableEntry uint32_t mPreferredLifetime; ///< Preferred lifetime of the on-link prefix when `mIsOnLink`. } otBorderRoutingPrefixTableEntry; +/** + * Represents information about a peer Border Router found in the Network Data. + * + */ +typedef struct otBorderRoutingPeerBorderRouterEntry +{ + uint16_t mRloc16; ///< The RLOC16 of BR. + uint32_t mAge; ///< Seconds since the BR appeared in the Network Data. +} otBorderRoutingPeerBorderRouterEntry; + /** * Represents a group of data of platform-generated RA messages processed. * @@ -488,6 +498,58 @@ otError otBorderRoutingGetNextRouterEntry(otInstance *aI otBorderRoutingPrefixTableIterator *aIterator, otBorderRoutingRouterEntry *aEntry); +/** + * Iterates over the peer BRs found in the Network Data. + * + * Requires `OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE`. + * + * Peer BRs are other devices within the Thread mesh that provide external IP connectivity. A device is considered + * to provide external IP connectivity if at least one of the following conditions is met regarding its Network Data + * entries: + * + * - It has added at least one external route entry. + * - It has added at least one prefix entry with both the default-route and on-mesh flags set. + * - It has added at least one domain prefix (with both the domain and on-mesh flags set). + * + * The list of peer BRs specifically excludes the current device, even if it is itself acting as a BR. + * + * @param[in] aInstance The OpenThread instance. + * @param[in,out] aIterator A pointer to the iterator. + * @param[out] aEntry A pointer to the entry to populate. + * + * @retval OT_ERROR_NONE Iterated to the next entry, @p aEntry and @p aIterator are updated. + * @retval OT_ERROR_NOT_FOUND No more entries. + * + */ +otError otBorderRoutingGetNextPeerBrEntry(otInstance *aInstance, + otBorderRoutingPrefixTableIterator *aIterator, + otBorderRoutingPeerBorderRouterEntry *aEntry); + +/** + * Returns the number of peer BRs found in the Network Data. + * + * Requires `OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE`. + * + * Peer BRs are other devices within the Thread mesh that provide external IP connectivity. A device is considered + * to provide external IP connectivity if at least one of the following conditions is met regarding its Network Data + * entries: + * + * - It has added at least one external route entry. + * - It has added at least one prefix entry with both the default-route and on-mesh flags set. + * - It has added at least one domain prefix (with both the domain and on-mesh flags set). + * + * The list of peer BRs specifically excludes the current device, even if it is itself acting as a BR. + * + * @param[in] aInstance The OpenThread instance. + * @param[out] aMinAge Pointer to an `uint32_t` to return the minimum age among all peer BRs. + * Can be NULL if the caller does not need this information. + * Age is represented as seconds since appearance of the BR entry in the Network Data. + * + * @returns The number of peer BRs. + * + */ +uint16_t otBorderRoutingCountPeerBrs(otInstance *aInstance, uint32_t *aMinAge); + /** * Enables / Disables DHCPv6 Prefix Delegation. * diff --git a/src/cli/README_BR.md b/src/cli/README_BR.md index 821dc269d..27e8b0864 100644 --- a/src/cli/README_BR.md +++ b/src/cli/README_BR.md @@ -13,6 +13,7 @@ Usage : `br [command] ...` - [omrprefix](#omrprefix) - [onlinkprefix](#onlinkprefix) - [pd](#pd) +- [peers](#peers) - [prefixtable](#prefixtable) - [rioprf](#rioprf) - [routeprf](#routeprf) @@ -35,6 +36,7 @@ enable omrprefix onlinkprefix pd +peers prefixtable raoptions rioprf @@ -221,6 +223,48 @@ Get the DHCPv6 Prefix Delegation (PD) provided off-mesh-routable (OMR) prefix. Done ``` +### peers + +Usage: `br peers` + +Get the list of peer BRs found in the Network Data. + +`OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE` is required. + +Peer BRs are other devices within the Thread mesh that provide external IP connectivity. A device is considered to provide external IP connectivity if at least one of the following conditions is met regarding its Network Data entries: + +- It has added at least one external route entry. +- It has added at least one prefix entry with both the default-route and on-mesh flags set. +- It has added at least one domain prefix (with both the domain and on-mesh flags set). + +The list of peer BRs specifically excludes the current device, even if it is itself acting as a BR. + +Info per BR entry: + +- RLOC16 of the BR +- Age as the duration interval since this BR appeared in Network Data. It is formatted as `{hh}:{mm}:{ss}` for hours, minutes, seconds, if the duration is less than 24 hours. If the duration is 24 hours or more, the format is `{dd}d.{hh}:{mm}:{ss}` for days, hours, minutes, seconds. + +```bash +> br peers +rloc16:0x5c00 age:00:00:49 +rloc16:0xf800 age:00:01:51 +Done +``` + +Usage: `br peers count` + +Gets the number of peer BRs found in the Network Data. + +The count does not include the current device, even if it is itself acting as a BR. + +The output indicates the minimum age among all peer BRs. Age is formatted as `{hh}:{mm}:{ss}` for hours, minutes, seconds, if the duration is less than 24 hours. If the duration is 24 hours or more, the format is `{dd}d.{hh}:{mm}:{ss}` for days, hours, minutes, seconds. + +```bash +> br peer count +2 min-age:00:00:49 +Done +``` + ### prefixtable Usage: `br prefixtable` diff --git a/src/cli/cli_br.cpp b/src/cli/cli_br.cpp index cab216d28..b49c774ce 100644 --- a/src/cli/cli_br.cpp +++ b/src/cli/cli_br.cpp @@ -374,6 +374,84 @@ exit: #endif // OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + +template <> otError Br::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + + /** + * @cli br peers + * @code + * br peers + * rloc16:0x5c00 age:00:00:49 + * rloc16:0xf800 age:00:01:51 + * Done + * @endcode + * @par + * Get the list of peer BRs found in Network Data entries. + * `OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE` is required. + * Peer BRs are other devices within the Thread mesh that provide external IP connectivity. A device is considered + * to provide external IP connectivity if at least one of the following conditions is met regarding its Network + * Data entries: + * - It has added at least one external route entry. + * - It has added at least one prefix entry with both the default-route and on-mesh flags set. + * - It has added at least one domain prefix (with both the domain and on-mesh flags set). + * The list of peer BRs specifically excludes the current device, even if its is itself acting as a BR. + * Info per BR entry: + * - RLOC16 of the BR + * - Age as the duration interval since this BR appeared in Network Data. It is formatted as `{hh}:{mm}:{ss}` for + * hours, minutes, seconds, if the duration is less than 24 hours. If the duration is 24 hours or more, the + * format is `{dd}d.{hh}:{mm}:{ss}` for days, hours, minutes, seconds. + * @sa otBorderRoutingGetNextPrefixTableEntry + */ + if (aArgs[0].IsEmpty()) + { + otBorderRoutingPrefixTableIterator iterator; + otBorderRoutingPeerBorderRouterEntry peerBrEntry; + char ageString[OT_DURATION_STRING_SIZE]; + + otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator); + + while (otBorderRoutingGetNextPeerBrEntry(GetInstancePtr(), &iterator, &peerBrEntry) == OT_ERROR_NONE) + { + otConvertDurationInSecondsToString(peerBrEntry.mAge, ageString, sizeof(ageString)); + OutputLine("rloc16:0x%04x age:%s", peerBrEntry.mRloc16, ageString); + } + } + /** + * @cli br peers count + * @code + * br peers count + * 2 min-age:00:00:47 + * Done + * @endcode + * @par api_copy + * #otBorderRoutingCountPeerBrs + */ + else if (aArgs[0] == "count") + { + uint32_t minAge; + uint16_t count; + char ageString[OT_DURATION_STRING_SIZE]; + + VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + count = otBorderRoutingCountPeerBrs(GetInstancePtr(), &minAge); + otConvertDurationInSecondsToString(minAge, ageString, sizeof(ageString)); + OutputLine("%u min-age:%s", count, ageString); + } + else + { + error = OT_ERROR_INVALID_ARGS; + } + +exit: + return error; +} + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + /** * @cli br prefixtable * @code @@ -791,6 +869,9 @@ otError Br::Process(Arg aArgs[]) CmdEntry("onlinkprefix"), #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE CmdEntry("pd"), +#endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + CmdEntry("peers"), #endif CmdEntry("prefixtable"), CmdEntry("raoptions"), diff --git a/src/core/api/border_routing_api.cpp b/src/core/api/border_routing_api.cpp index 8bc2b7af0..a7e477f8c 100644 --- a/src/core/api/border_routing_api.cpp +++ b/src/core/api/border_routing_api.cpp @@ -194,6 +194,28 @@ otError otBorderRoutingGetNextRouterEntry(otInstance *aI return AsCoreType(aInstance).Get().GetNextRouterEntry(*aIterator, *aEntry); } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + +otError otBorderRoutingGetNextPeerBrEntry(otInstance *aInstance, + otBorderRoutingPrefixTableIterator *aIterator, + otBorderRoutingPeerBorderRouterEntry *aEntry) +{ + AssertPointerIsNotNull(aIterator); + AssertPointerIsNotNull(aEntry); + + return AsCoreType(aInstance).Get().GetNextPeerBrEntry(*aIterator, *aEntry); +} + +uint16_t otBorderRoutingCountPeerBrs(otInstance *aInstance, uint32_t *aMinAge) +{ + uint32_t minAge; + + return AsCoreType(aInstance).Get().CountPeerBrs((aMinAge != nullptr) ? *aMinAge + : minAge); +} + +#endif + #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE void otBorderRoutingDhcp6PdSetEnabled(otInstance *aInstance, bool aEnabled) { diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index 86d35cc8a..02c930ae2 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -75,6 +75,9 @@ RoutingManager::RoutingManager(Instance &aInstance) , mOmrPrefixManager(aInstance) , mRioAdvertiser(aInstance) , mOnLinkPrefixManager(aInstance) +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + , mNetDataPeerBrTracker(aInstance) +#endif , mRxRaTracker(aInstance) , mRoutePublisher(aInstance) #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE @@ -438,6 +441,10 @@ void RoutingManager::HandleNotifierEvents(Events aEvents) mRoutePublisher.HandleNotifierEvents(aEvents); +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + mNetDataPeerBrTracker.HandleNotifierEvents(aEvents); +#endif + VerifyOrExit(IsInitialized() && IsEnabled()); if (aEvents.Contains(kEventThreadRoleChanged)) @@ -982,6 +989,91 @@ void RoutingManager::RoutePrefix::CopyInfoTo(PrefixTableEntry &aEntry, TimeMilli aEntry.mRoutePreference = static_cast(GetRoutePreference()); } +//--------------------------------------------------------------------------------------------------------------------- +// NetDataPeerBrTracker + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE +RoutingManager::NetDataPeerBrTracker::NetDataPeerBrTracker(Instance &aInstance) + : InstanceLocator(aInstance) +{ +} + +uint16_t RoutingManager::NetDataPeerBrTracker::CountPeerBrs(uint32_t &aMinAge) const +{ + uint32_t uptime = Uptime::MsecToSec(Get().GetUptime()); + uint16_t count = 0; + + aMinAge = NumericLimits::kMax; + + for (const PeerBr &peerBr : mPeerBrs) + { + count++; + aMinAge = Min(aMinAge, peerBr.GetAge(uptime)); + } + + if (count == 0) + { + aMinAge = 0; + } + + return count; +} + +Error RoutingManager::NetDataPeerBrTracker::GetNext(PrefixTableIterator &aIterator, PeerBrEntry &aEntry) const +{ + using Iterator = RxRaTracker::Iterator; + + Iterator &iterator = static_cast(aIterator); + Error error; + + SuccessOrExit(error = iterator.AdvanceToNextPeerBr(mPeerBrs.GetHead())); + + aEntry.mRloc16 = iterator.GetPeerBrEntry()->mRloc16; + aEntry.mAge = iterator.GetPeerBrEntry()->GetAge(iterator.GetInitUptime()); + +exit: + return error; +} + +void RoutingManager::NetDataPeerBrTracker::HandleNotifierEvents(Events aEvents) +{ + NetworkData::Rlocs rlocs; + + VerifyOrExit(aEvents.ContainsAny(kEventThreadNetdataChanged | kEventThreadRoleChanged)); + + Get().FindRlocs(NetworkData::kBrProvidingExternalIpConn, NetworkData::kAnyRole, rlocs); + + // Remove `PeerBr` entries no longer found in Network Data, + // or they match the device RLOC16. Then allocate and add + // entries for newly discovered peers. + + mPeerBrs.RemoveAndFreeAllMatching(PeerBr::Filter(rlocs)); + mPeerBrs.RemoveAndFreeAllMatching(Get().GetRloc16()); + + for (uint16_t rloc16 : rlocs) + { + PeerBr *newEntry; + + if (Get().HasRloc16(rloc16) || mPeerBrs.ContainsMatching(rloc16)) + { + continue; + } + + newEntry = PeerBr::Allocate(); + VerifyOrExit(newEntry != nullptr, LogWarn("Failed to allocate `PeerBr` entry")); + + newEntry->mRloc16 = rloc16; + newEntry->mDiscoverTime = Uptime::MsecToSec(Get().GetUptime()); + + mPeerBrs.Push(*newEntry); + } + +exit: + return; +} + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + //--------------------------------------------------------------------------------------------------------------------- // RxRaTracker @@ -1850,6 +1942,32 @@ exit: return error; } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + +Error RoutingManager::RxRaTracker::Iterator::AdvanceToNextPeerBr(const PeerBr *aPeerBrsHead) +{ + Error error = kErrorNone; + + if (GetType() == kUnspecified) + { + SetType(kPeerBrIterator); + SetEntry(aPeerBrsHead); + } + else + { + VerifyOrExit(GetType() == kPeerBrIterator, error = kErrorInvalidArgs); + VerifyOrExit(GetPeerBrEntry() != nullptr, error = kErrorNotFound); + SetEntry(GetPeerBrEntry()->GetNext()); + } + + VerifyOrExit(GetPeerBrEntry() != nullptr, error = kErrorNotFound); + +exit: + return error; +} + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + //--------------------------------------------------------------------------------------------------------------------- // RxRaTracker::Router diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index c9753b88a..c6e4c8dff 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -51,6 +51,10 @@ #error "OPENTHREAD_CONFIG_UPTIME_ENABLE is required for OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE" #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE && !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE +#error "TRACK_PEER_BR_INFO_ENABLE feature requires OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE" +#endif + #include #include #include @@ -78,7 +82,6 @@ #include "thread/network_data.hpp" namespace ot { - namespace BorderRouter { extern "C" void otPlatBorderRoutingProcessIcmp6Ra(otInstance *aInstance, const uint8_t *aMessage, uint16_t aLength); @@ -108,6 +111,7 @@ public: typedef otBorderRoutingPrefixTableIterator PrefixTableIterator; ///< Prefix Table Iterator. typedef otBorderRoutingPrefixTableEntry PrefixTableEntry; ///< Prefix Table Entry. typedef otBorderRoutingRouterEntry RouterEntry; ///< Router Entry. + typedef otBorderRoutingPeerBorderRouterEntry PeerBrEntry; ///< Peer Border Router Entry. typedef otPdProcessedRaInfo PdProcessedRaInfo; ///< Data of PdProcessedRaInfo. typedef otBorderRoutingRequestDhcp6PdCallback PdCallback; ///< DHCPv6 PD callback. @@ -501,6 +505,38 @@ public: return mRxRaTracker.GetNextRouter(aIterator, aEntry); } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + + /** + * Iterates over the peer BRs found in the Network Data. + * + * @param[in,out] aIterator An iterator. + * @param[out] aEntry A reference to the entry to populate. + * + * @retval kErrorNone Got the next peer BR info, @p aEntry is updated and @p aIterator is advanced. + * @retval kErrorNotFound No more PR beers in the list. + * + */ + Error GetNextPeerBrEntry(PrefixTableIterator &aIterator, PeerBrEntry &aEntry) const + { + return mNetDataPeerBrTracker.GetNext(aIterator, aEntry); + } + + /** + * Returns the number of peer BRs found in the Network Data. + * + * The count does not include this device itself (when it itself is acting as a BR). + * + * @param[out] aMinAge Reference to an `uint32_t` to return the minimum age among all peer BRs. + * Age is represented as seconds since appearance of the BR entry in the Network Data. + * + * @returns The number of peer BRs. + * + */ + uint16_t CountPeerBrs(uint32_t &aMinAge) const { return mNetDataPeerBrTracker.CountPeerBrs(aMinAge); } + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE /** * Determines whether to enable/disable SRP server when the auto-enable mode is changed on SRP server. @@ -736,6 +772,51 @@ private: //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + + class RxRaTracker; + + class NetDataPeerBrTracker : public InstanceLocator + { + friend class RxRaTracker; + + public: + explicit NetDataPeerBrTracker(Instance &aInstance); + + uint16_t CountPeerBrs(uint32_t &aMinAge) const; + Error GetNext(PrefixTableIterator &aIterator, PeerBrEntry &aEntry) const; + + void HandleNotifierEvents(Events aEvents); + + private: + struct PeerBr : LinkedListEntry, Heap::Allocatable + { + struct Filter + { + Filter(const NetworkData::Rlocs &aRlocs) + : mExcludeRlocs(aRlocs) + { + } + + const NetworkData::Rlocs &mExcludeRlocs; + }; + + uint32_t GetAge(uint32_t aUptime) const { return aUptime - mDiscoverTime; } + bool Matches(uint16_t aRloc16) const { return mRloc16 == aRloc16; } + bool Matches(const Filter &aFilter) const { return !aFilter.mExcludeRlocs.Contains(mRloc16); } + + PeerBr *mNext; + uint16_t mRloc16; + uint32_t mDiscoverTime; + }; + + OwningList mPeerBrs; + }; + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + void HandleRxRaTrackerSignalTask(void) { mRxRaTracker.HandleSignalTask(); } void HandleRxRaTrackerExpirationTimer(void) { mRxRaTracker.HandleExpirationTimer(); } void HandleRxRaTrackerStaleTimer(void) { mRxRaTracker.HandleStaleTimer(); } @@ -755,6 +836,8 @@ private: // the same flow of execution, the callback is invoked after all the // changes are processed. + friend class NetDataPeerBrTracker; + public: explicit RxRaTracker(Instance &aInstance); @@ -881,6 +964,7 @@ private: kUnspecified, kRouterIterator, kPrefixIterator, + kPeerBrIterator, }; enum EntryType : uint8_t @@ -903,6 +987,13 @@ private: return static_cast *>(mPtr2); } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + using PeerBr = NetDataPeerBrTracker::PeerBr; + + Error AdvanceToNextPeerBr(const PeerBr *aPeerBrsHead); + const PeerBr *GetPeerBrEntry(void) const { return static_cast(mPtr2); } +#endif + private: void SetRouter(const Entry *aRouter) { mPtr1 = aRouter; } void SetInitUptime(uint32_t aUptime) { mData0 = aUptime; } @@ -1487,6 +1578,10 @@ private: OnLinkPrefixManager mOnLinkPrefixManager; +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + NetDataPeerBrTracker mNetDataPeerBrTracker; +#endif + RxRaTracker mRxRaTracker; RoutePublisher mRoutePublisher; diff --git a/tests/toranj/cli/cli.py b/tests/toranj/cli/cli.py index 8f278f437..8e02b65b3 100644 --- a/tests/toranj/cli/cli.py +++ b/tests/toranj/cli/cli.py @@ -805,6 +805,12 @@ class Node(object): def br_get_routers(self): return self.cli('br routers') + def br_get_peer_brs(self): + return self.cli('br peers') + + def br_count_peers(self): + return self._cli_single_output('br peers count') + # ------------------------------------------------------------------------------------------------------------------ # Helper methods diff --git a/tests/toranj/cli/test-503-peer-tbr-discovery.py b/tests/toranj/cli/test-503-peer-tbr-discovery.py index d6816a4bf..2cc58d42f 100755 --- a/tests/toranj/cli/test-503-peer-tbr-discovery.py +++ b/tests/toranj/cli/test-503-peer-tbr-discovery.py @@ -104,11 +104,33 @@ verify(br3.br_get_state() == 'running') # Validate that all BRs discovered the other ones as peer BR -for br in [br1, br2, br3]: +all_brs = [br1, br2, br3] + +for br in all_brs: routers = br.br_get_routers() verify(len(routers) == 2) for router in routers: verify(router.endswith('(peer BR)')) + verify(int(br.br_count_peers().split()[0]) == 2) + peers = br.br_get_peer_brs() + verify(len(peers) == 2) + other_brs = all_brs + other_brs.remove(br) + for other_br in other_brs: + rloc16 = other_br.get_rloc16() + verify(any([rloc16 in peer for peer in peers])) + +# Disable BR3 and validate that BR1 and BR2 detect this. +# BR3 itself should continue to detect BR1 and BR2 + +br3.br_disable() + +time.sleep(0.5) + +for br in [br1, br2]: + verify(len(br.br_get_peer_brs()) == 1) + +verify(len(br3.br_get_peer_brs()) == 2) # ----------------------------------------------------------------------------------------------------------------------- # Test finished