From 422f673c7c1127ee82b99667c4d3007fb9c60ba2 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Sat, 1 Jul 2023 07:50:01 -0700 Subject: [PATCH] [routing-manager] determine route preference from parent link quality (#9080) This commit updates how `RoutePublisher` determines the published route preference: - Medium preference is used on a border router (BR) acting as a router or a BR acting as an end-device (ED) connected to a parent with link quality 3. - An ED BR with lower link quality publishes its route with low preference. For ED BRs, the preference is updated if the parent's link quality changes. However, to avoid frequent preference changes due to link variability, the following rules are used: - If the link quality goes to 1 or 2, the route preference is immediately changed to low. - On transition to link quality 3, we wait for 5 minutes before changing the preference to medium. - If the ED BR switches parents, the link quality to the new parent is used to determine preference. It also adds new APIs and related CLI commands to allow users to get and manually set the published route preference. A new test script `test-021-br-route-prf.py` is added to validate the selection of route preference by BR. This commit also adds a new mechanism to detect when the link quality to the parent changes and signal it using a newly added `Notifier` event. --- examples/platforms/simulation/infra_if.c | 4 +- include/openthread/border_routing.h | 38 +++ include/openthread/instance.h | 72 +++--- src/cli/README_BR.md | 36 +++ src/cli/cli_br.cpp | 81 +++++++ src/core/api/border_routing_api.cpp | 17 ++ src/core/border_router/routing_manager.cpp | 43 +++- src/core/border_router/routing_manager.hpp | 43 +++- src/core/common/notifier.cpp | 1 + src/core/common/notifier.hpp | 1 + src/core/mac/mac.cpp | 33 ++- src/core/mac/mac.hpp | 1 + tests/toranj/cli/cli.py | 24 ++ tests/toranj/cli/test-021-br-route-prf.py | 263 +++++++++++++++++++++ tests/toranj/start.sh | 1 + 15 files changed, 602 insertions(+), 56 deletions(-) create mode 100755 tests/toranj/cli/test-021-br-route-prf.py diff --git a/examples/platforms/simulation/infra_if.c b/examples/platforms/simulation/infra_if.c index 1904d75d6..9596406d9 100644 --- a/examples/platforms/simulation/infra_if.c +++ b/examples/platforms/simulation/infra_if.c @@ -49,13 +49,13 @@ otError otPlatInfraIfSendIcmp6Nd(uint32_t aInfraIfIndex, OT_UNUSED_VARIABLE(aBuffer); OT_UNUSED_VARIABLE(aBufferLength); - return OT_ERROR_FAILED; + return OT_ERROR_NONE; } otError otPlatInfraIfDiscoverNat64Prefix(uint32_t aInfraIfIndex) { OT_UNUSED_VARIABLE(aInfraIfIndex); - return OT_ERROR_FAILED; + return OT_ERROR_NONE; } #endif diff --git a/include/openthread/border_routing.h b/include/openthread/border_routing.h index 2de6dde3a..8c679ac06 100644 --- a/include/openthread/border_routing.h +++ b/include/openthread/border_routing.h @@ -213,6 +213,44 @@ void otBorderRoutingSetRouteInfoOptionPreference(otInstance *aInstance, otRouteP */ void otBorderRoutingClearRouteInfoOptionPreference(otInstance *aInstance); +/** + * Gets the current preference used for published routes in Network Data. + * + * The preference is determined as follows: + * + * - If explicitly set by user by calling `otBorderRoutingSetRoutePreference()`, the given preference is used. + * - Otherwise, it is determined automatically by `RoutingManager` based on the device's role and link quality. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns The current published route preference. + * + */ +otRoutePreference otBorderRoutingGetRoutePreference(otInstance *aInstance); + +/** + * Explicitly sets the preference of published routes in Network Data. + * + * After a call to this function, BR will use the given preference. The preference can be cleared by calling + * `otBorderRoutingClearRoutePreference()`. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aPreference The route preference to use. + * + */ +void otBorderRoutingSetRoutePreference(otInstance *aInstance, otRoutePreference aPreference); + +/** + * Clears a previously set preference value for published routes in Network Data. + * + * After a call to this function, BR will determine the preference automatically based on the device's role and + * link quality (to the parent when acting as end-device). + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + */ +void otBorderRoutingClearRoutePreference(otInstance *aInstance); + /** * Gets the local Off-Mesh-Routable (OMR) Prefix, for example `fdfc:1ff5:1512:5622::/64`. * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 61f4bd7ca..15e4b7a23 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 (336) +#define OPENTHREAD_API_VERSION (337) /** * @addtogroup api-instance @@ -171,44 +171,38 @@ uint64_t otInstanceGetUptime(otInstance *aInstance); */ void otInstanceGetUptimeAsString(otInstance *aInstance, char *aBuffer, uint16_t aSize); -/** - * Defines flags that are passed as part of `otStateChangedCallback`. - * - */ -enum -{ - OT_CHANGED_IP6_ADDRESS_ADDED = 1 << 0, ///< IPv6 address was added - OT_CHANGED_IP6_ADDRESS_REMOVED = 1 << 1, ///< IPv6 address was removed - OT_CHANGED_THREAD_ROLE = 1 << 2, ///< Role (disabled, detached, child, router, leader) changed - OT_CHANGED_THREAD_LL_ADDR = 1 << 3, ///< The link-local address changed - OT_CHANGED_THREAD_ML_ADDR = 1 << 4, ///< The mesh-local address changed - OT_CHANGED_THREAD_RLOC_ADDED = 1 << 5, ///< RLOC was added - OT_CHANGED_THREAD_RLOC_REMOVED = 1 << 6, ///< RLOC was removed - OT_CHANGED_THREAD_PARTITION_ID = 1 << 7, ///< Partition ID changed - OT_CHANGED_THREAD_KEY_SEQUENCE_COUNTER = 1 << 8, ///< Thread Key Sequence changed - OT_CHANGED_THREAD_NETDATA = 1 << 9, ///< Thread Network Data changed - OT_CHANGED_THREAD_CHILD_ADDED = 1 << 10, ///< Child was added - OT_CHANGED_THREAD_CHILD_REMOVED = 1 << 11, ///< Child was removed - OT_CHANGED_IP6_MULTICAST_SUBSCRIBED = 1 << 12, ///< Subscribed to a IPv6 multicast address - OT_CHANGED_IP6_MULTICAST_UNSUBSCRIBED = 1 << 13, ///< Unsubscribed from a IPv6 multicast address - OT_CHANGED_THREAD_CHANNEL = 1 << 14, ///< Thread network channel changed - OT_CHANGED_THREAD_PANID = 1 << 15, ///< Thread network PAN Id changed - OT_CHANGED_THREAD_NETWORK_NAME = 1 << 16, ///< Thread network name changed - OT_CHANGED_THREAD_EXT_PANID = 1 << 17, ///< Thread network extended PAN ID changed - OT_CHANGED_NETWORK_KEY = 1 << 18, ///< Network key changed - OT_CHANGED_PSKC = 1 << 19, ///< PSKc changed - OT_CHANGED_SECURITY_POLICY = 1 << 20, ///< Security Policy changed - OT_CHANGED_CHANNEL_MANAGER_NEW_CHANNEL = 1 << 21, ///< Channel Manager new pending Thread channel changed - OT_CHANGED_SUPPORTED_CHANNEL_MASK = 1 << 22, ///< Supported channel mask changed - OT_CHANGED_COMMISSIONER_STATE = 1 << 23, ///< Commissioner state changed - OT_CHANGED_THREAD_NETIF_STATE = 1 << 24, ///< Thread network interface state changed - OT_CHANGED_THREAD_BACKBONE_ROUTER_STATE = 1 << 25, ///< Backbone Router state changed - OT_CHANGED_THREAD_BACKBONE_ROUTER_LOCAL = 1 << 26, ///< Local Backbone Router configuration changed - OT_CHANGED_JOINER_STATE = 1 << 27, ///< Joiner state changed - OT_CHANGED_ACTIVE_DATASET = 1 << 28, ///< Active Operational Dataset changed - OT_CHANGED_PENDING_DATASET = 1 << 29, ///< Pending Operational Dataset changed - OT_CHANGED_NAT64_TRANSLATOR_STATE = 1 << 30, ///< The state of NAT64 translator changed -}; +#define OT_CHANGED_IP6_ADDRESS_ADDED (1U << 0) ///< IPv6 address was added +#define OT_CHANGED_IP6_ADDRESS_REMOVED (1U << 1) ///< IPv6 address was removed +#define OT_CHANGED_THREAD_ROLE (1U << 2) ///< Role (disabled, detached, child, router, leader) changed +#define OT_CHANGED_THREAD_LL_ADDR (1U << 3) ///< The link-local address changed +#define OT_CHANGED_THREAD_ML_ADDR (1U << 4) ///< The mesh-local address changed +#define OT_CHANGED_THREAD_RLOC_ADDED (1U << 5) ///< RLOC was added +#define OT_CHANGED_THREAD_RLOC_REMOVED (1U << 6) ///< RLOC was removed +#define OT_CHANGED_THREAD_PARTITION_ID (1U << 7) ///< Partition ID changed +#define OT_CHANGED_THREAD_KEY_SEQUENCE_COUNTER (1U << 8) ///< Thread Key Sequence changed +#define OT_CHANGED_THREAD_NETDATA (1U << 9) ///< Thread Network Data changed +#define OT_CHANGED_THREAD_CHILD_ADDED (1U << 10) ///< Child was added +#define OT_CHANGED_THREAD_CHILD_REMOVED (1U << 11) ///< Child was removed +#define OT_CHANGED_IP6_MULTICAST_SUBSCRIBED (1U << 12) ///< Subscribed to a IPv6 multicast address +#define OT_CHANGED_IP6_MULTICAST_UNSUBSCRIBED (1U << 13) ///< Unsubscribed from a IPv6 multicast address +#define OT_CHANGED_THREAD_CHANNEL (1U << 14) ///< Thread network channel changed +#define OT_CHANGED_THREAD_PANID (1U << 15) ///< Thread network PAN Id changed +#define OT_CHANGED_THREAD_NETWORK_NAME (1U << 16) ///< Thread network name changed +#define OT_CHANGED_THREAD_EXT_PANID (1U << 17) ///< Thread network extended PAN ID changed +#define OT_CHANGED_NETWORK_KEY (1U << 18) ///< Network key changed +#define OT_CHANGED_PSKC (1U << 19) ///< PSKc changed +#define OT_CHANGED_SECURITY_POLICY (1U << 20) ///< Security Policy changed +#define OT_CHANGED_CHANNEL_MANAGER_NEW_CHANNEL (1U << 21) ///< Channel Manager new pending Thread channel changed +#define OT_CHANGED_SUPPORTED_CHANNEL_MASK (1U << 22) ///< Supported channel mask changed +#define OT_CHANGED_COMMISSIONER_STATE (1U << 23) ///< Commissioner state changed +#define OT_CHANGED_THREAD_NETIF_STATE (1U << 24) ///< Thread network interface state changed +#define OT_CHANGED_THREAD_BACKBONE_ROUTER_STATE (1U << 25) ///< Backbone Router state changed +#define OT_CHANGED_THREAD_BACKBONE_ROUTER_LOCAL (1U << 26) ///< Local Backbone Router configuration changed +#define OT_CHANGED_JOINER_STATE (1U << 27) ///< Joiner state changed +#define OT_CHANGED_ACTIVE_DATASET (1U << 28) ///< Active Operational Dataset changed +#define OT_CHANGED_PENDING_DATASET (1U << 29) ///< Pending Operational Dataset changed +#define OT_CHANGED_NAT64_TRANSLATOR_STATE (1U << 30) ///< The state of NAT64 translator changed +#define OT_CHANGED_PARENT_LINK_QUALITY (1U << 31) ///< Parent link quality changed /** * Represents a bit-field indicating specific state/configuration that has changed. See `OT_CHANGED_*` diff --git a/src/cli/README_BR.md b/src/cli/README_BR.md index 90a50b619..4761d8792 100644 --- a/src/cli/README_BR.md +++ b/src/cli/README_BR.md @@ -13,6 +13,7 @@ Usage : `br [command] ...` - [onlinkprefix](#onlinkprefix) - [prefixtable](#prefixtable) - [rioprf](#rioprf) +- [routeprf](#routeprf) - [state](#state) ## Command Details @@ -32,6 +33,7 @@ omrprefix onlinkprefix prefixtable rioprf +routeprf state Done ``` @@ -206,3 +208,37 @@ Clear a previously set preference value for advertising Route Info Options (e.g. > br rioprf clear Done ``` + +### routeprf + +Usage: `br routeprf` + +Get the preference used for publishing routes in Thread Network Data. This may be the automatically determined route preference, or an administratively set fixed route preference - if applicable. + +```bash +> br routeprf +med +Done +``` + +### routeprf \ + +Usage: `br routeprf high|med|low` + +Set the preference (which may be 'high', 'med', or 'low') to use publishing routes in Thread Network Data. Setting a preference value overrides the automatic route preference determination. It is used only for an explicit administrative configuration of a Border Router. + +```bash +> br routeprf low +Done +``` + +### routeprf clear + +Usage: `br routeprf clear` + +Clear a previously set preference value for publishing routes in Thread Network Data. When cleared BR will automatically determine the route preference based on device's role and link quality to parent (when acting as end-device). + +```bash +> br routeprf clear +Done +``` diff --git a/src/cli/cli_br.cpp b/src/cli/cli_br.cpp index a3e2152e5..d1fd81b4d 100644 --- a/src/cli/cli_br.cpp +++ b/src/cli/cli_br.cpp @@ -44,6 +44,32 @@ namespace ot { namespace Cli { +/** + * @cli br init + * @code + * br init 2 1 + * Done + * @endcode + * @cparam br init @ca{infrastructure-network-index} @ca{is-running} + * @par + * Initializes the Border Routing Manager. + * @sa otBorderRoutingInit + */ +template <> otError Br::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + uint32_t ifIndex; + bool isRunning; + + SuccessOrExit(error = aArgs[0].ParseAsUint32(ifIndex)); + SuccessOrExit(error = aArgs[1].ParseAsBool(isRunning)); + VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + error = otBorderRoutingInit(GetInstancePtr(), ifIndex, isRunning); + +exit: + return error; +} + /** * @cli br enable * @code @@ -452,6 +478,59 @@ exit: return error; } +template <> otError Br::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + + /** + * @cli br routeprf + * @code + * br routeprf + * med + * Done + * @endcode + * @par api_copy + * #otBorderRoutingGetRoutePreference + */ + if (aArgs[0].IsEmpty()) + { + OutputLine("%s", Interpreter::PreferenceToString(otBorderRoutingGetRoutePreference(GetInstancePtr()))); + } + /** + * @cli br routeprf clear + * @code + * br routeprf clear + * Done + * @endcode + * @par api_copy + * #otBorderRoutingClearRoutePreference + */ + else if (aArgs[0] == "clear") + { + otBorderRoutingClearRoutePreference(GetInstancePtr()); + } + /** + * @cli br routeprf (high,med,low) + * @code + * br routeprf low + * Done + * @endcode + * @cparam br routeprf [@ca{high}|@ca{med}|@ca{low}] + * @par api_copy + * #otBorderRoutingSetRoutePreference + */ + else + { + otRoutePreference preference; + + SuccessOrExit(error = Interpreter::ParsePreference(aArgs[0], preference)); + otBorderRoutingSetRoutePreference(GetInstancePtr(), preference); + } + +exit: + return error; +} + #if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE /** @@ -499,6 +578,7 @@ otError Br::Process(Arg aArgs[]) #endif CmdEntry("disable"), CmdEntry("enable"), + CmdEntry("init"), #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE CmdEntry("nat64prefix"), #endif @@ -506,6 +586,7 @@ otError Br::Process(Arg aArgs[]) CmdEntry("onlinkprefix"), CmdEntry("prefixtable"), CmdEntry("rioprf"), + CmdEntry("routeprf"), CmdEntry("state"), }; diff --git a/src/core/api/border_routing_api.cpp b/src/core/api/border_routing_api.cpp index 1e2c8de28..1ed017c57 100644 --- a/src/core/api/border_routing_api.cpp +++ b/src/core/api/border_routing_api.cpp @@ -75,6 +75,23 @@ void otBorderRoutingClearRouteInfoOptionPreference(otInstance *aInstance) AsCoreType(aInstance).Get().ClearRouteInfoOptionPreference(); } +otRoutePreference otBorderRoutingGetRoutePreference(otInstance *aInstance) +{ + return static_cast( + AsCoreType(aInstance).Get().GetRoutePreference()); +} + +void otBorderRoutingSetRoutePreference(otInstance *aInstance, otRoutePreference aPreference) +{ + AsCoreType(aInstance).Get().SetRoutePreference( + static_cast(aPreference)); +} + +void otBorderRoutingClearRoutePreference(otInstance *aInstance) +{ + AsCoreType(aInstance).Get().ClearRoutePreference(); +} + otError otBorderRoutingGetOmrPrefix(otInstance *aInstance, otIp6Prefix *aPrefix) { return AsCoreType(aInstance).Get().GetOmrPrefix(AsCoreType(aPrefix)); diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index 41ae6ba60..e061fb37c 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -433,9 +433,10 @@ void RoutingManager::HandleNotifierEvents(Events aEvents) if (aEvents.Contains(kEventThreadRoleChanged) && !mUserSetRioPreference) { SetRioPreferenceBasedOnRole(); - mRoutePublisher.HandleRoleChanged(); } + mRoutePublisher.HandleNotifierEvents(aEvents); + VerifyOrExit(IsInitialized() && IsEnabled()); if (aEvents.Contains(kEventThreadRoleChanged)) @@ -2725,6 +2726,7 @@ RoutingManager::RoutePublisher::RoutePublisher(Instance &aInstance) , mState(kDoNotPublish) , mPreference(NetworkData::kRoutePreferenceMedium) , mUserSetPreference(false) + , mTimer(aInstance) { } @@ -2838,6 +2840,7 @@ void RoutingManager::RoutePublisher::SetPreference(RoutePreference aPreference) { LogInfo("User explicitly set published route preference to %s", RoutePreferenceToString(aPreference)); mUserSetPreference = true; + mTimer.Stop(); UpdatePreference(aPreference); } @@ -2855,18 +2858,48 @@ exit: void RoutingManager::RoutePublisher::SetPreferenceBasedOnRole(void) { - UpdatePreference(Get().IsRouterOrLeader() ? NetworkData::kRoutePreferenceMedium - : NetworkData::kRoutePreferenceLow); + RoutePreference preference = NetworkData::kRoutePreferenceMedium; + + if (Get().IsChild() && (Get().GetParent().GetTwoWayLinkQuality() != kLinkQuality3)) + { + preference = NetworkData::kRoutePreferenceLow; + } + + UpdatePreference(preference); + mTimer.Stop(); } -void RoutingManager::RoutePublisher::HandleRoleChanged(void) +void RoutingManager::RoutePublisher::HandleNotifierEvents(Events aEvents) { - if (!mUserSetPreference) + VerifyOrExit(!mUserSetPreference); + + if (aEvents.Contains(kEventThreadRoleChanged)) { SetPreferenceBasedOnRole(); } + + if (aEvents.Contains(kEventParentLinkQualityChanged)) + { + VerifyOrExit(Get().IsChild()); + + if (Get().GetParent().GetTwoWayLinkQuality() == kLinkQuality3) + { + VerifyOrExit(!mTimer.IsRunning()); + mTimer.Start(kDelayBeforePrfUpdateOnLinkQuality3); + } + else + { + UpdatePreference(NetworkData::kRoutePreferenceLow); + mTimer.Stop(); + } + } + +exit: + return; } +void RoutingManager::RoutePublisher::HandleTimer(void) { SetPreferenceBasedOnRole(); } + void RoutingManager::RoutePublisher::UpdatePreference(RoutePreference aPreference) { VerifyOrExit(mPreference != aPreference); diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index b1f554b46..f8eab50f8 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -228,6 +228,39 @@ public: */ void ClearRouteInfoOptionPreference(void); + /** + * Gets the current preference used for published routes in Network Data. + * + * The preference is determined as follows: + * + * - If explicitly set by user by calling `SetRoutePreference()`, the given preference is used. + * - Otherwise, it is determined automatically by `RoutingManager` based on the device's role and link quality. + * + * @returns The current published route preference. + * + */ + RoutePreference GetRoutePreference(void) const { return mRoutePublisher.GetPreference(); } + + /** + * Explicitly sets the preference of published routes in Network Data. + * + * After a call to this method, BR will use the given preference. The preference can be cleared by calling + * `ClearRoutePreference`()`. + * + * @param[in] aPreference The route preference to use. + * + */ + void SetRoutePreference(RoutePreference aPreference) { mRoutePublisher.SetPreference(aPreference); } + + /** + * Clears a previously set preference value for published routes in Network Data. + * + * After a call to this method, BR will determine the preference automatically based on the device's role and + * link quality (to the parent when acting as end-device). + * + */ + void ClearRoutePreference(void) { mRoutePublisher.ClearPreference(); } + /** * Returns the local generated off-mesh-routable (OMR) prefix. * @@ -941,6 +974,8 @@ private: }; #endif // OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE + void HandleRoutePublisherTimer(void) { mRoutePublisher.HandleTimer(); } + class RoutePublisher : public InstanceLocator // Manages the routes that are published in net data { public: @@ -954,11 +989,14 @@ private: void SetPreference(RoutePreference aPreference); void ClearPreference(void); - void HandleRoleChanged(void); + void HandleNotifierEvents(Events aEvents); + void HandleTimer(void); static const Ip6::Prefix &GetUlaPrefix(void) { return AsCoreType(&kUlaPrefix); } private: + static constexpr uint32_t kDelayBeforePrfUpdateOnLinkQuality3 = TimeMilli::SecToMsec(5 * 60); + static const otIp6Prefix kUlaPrefix; enum State : uint8_t @@ -976,9 +1014,12 @@ private: static const char *StateToString(State aState); + using DelayTimer = TimerMilliIn; + State mState; RoutePreference mPreference; bool mUserSetPreference; + DelayTimer mTimer; }; struct RaInfo diff --git a/src/core/common/notifier.cpp b/src/core/common/notifier.cpp index 706350bba..7f7646f1a 100644 --- a/src/core/common/notifier.cpp +++ b/src/core/common/notifier.cpp @@ -272,6 +272,7 @@ const char *Notifier::EventToString(Event aEvent) const "ActDset", // kEventActiveDatasetChanged (1 << 28) "PndDset", // kEventPendingDatasetChanged (1 << 29) "Nat64", // kEventNat64TranslatorStateChanged (1 << 30) + "ParentLq", // kEventParentLinkQualityChanged (1 << 31) }; for (uint8_t index = 0; index < GetArrayLength(kEventStrings); index++) diff --git a/src/core/common/notifier.hpp b/src/core/common/notifier.hpp index 02efc21d3..0bd8effbe 100644 --- a/src/core/common/notifier.hpp +++ b/src/core/common/notifier.hpp @@ -97,6 +97,7 @@ enum Event : uint32_t kEventActiveDatasetChanged = OT_CHANGED_ACTIVE_DATASET, ///< Active Dataset changed kEventPendingDatasetChanged = OT_CHANGED_PENDING_DATASET, ///< Pending Dataset changed kEventNat64TranslatorStateChanged = OT_CHANGED_NAT64_TRANSLATOR_STATE, ///< Nat64Translator state changed + kEventParentLinkQualityChanged = OT_CHANGED_PARENT_LINK_QUALITY, ///< Parent link quality changed }; /** diff --git a/src/core/mac/mac.cpp b/src/core/mac/mac.cpp index fcf3ea37f..4131b2754 100644 --- a/src/core/mac/mac.cpp +++ b/src/core/mac/mac.cpp @@ -1298,11 +1298,8 @@ void Mac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aError) if ((aError == kErrorNone) && (neighbor != nullptr)) { - neighbor->GetLinkInfo().AddRss(aAckFrame->GetRssi()); -#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE - neighbor->AggregateLinkMetrics(/* aSeriesId */ 0, aAckFrame->GetType(), aAckFrame->GetLqi(), - aAckFrame->GetRssi()); -#endif + UpdateNeighborLinkInfo(*neighbor, *aAckFrame); + #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE ProcessEnhAckProbing(*aAckFrame, *neighbor); #endif @@ -1863,10 +1860,7 @@ void Mac::HandleReceivedFrame(RxFrame *aFrame, Error aError) if (neighbor != nullptr) { - neighbor->GetLinkInfo().AddRss(aFrame->GetRssi()); -#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE - neighbor->AggregateLinkMetrics(/* aSeriesId */ 0, aFrame->GetType(), aFrame->GetLqi(), aFrame->GetRssi()); -#endif + UpdateNeighborLinkInfo(*neighbor, *aFrame); if (aFrame->GetSecurityEnabled()) { @@ -2030,6 +2024,27 @@ exit: } } +void Mac::UpdateNeighborLinkInfo(Neighbor &aNeighbor, const RxFrame &aRxFrame) +{ + LinkQuality oldLinkQuality = aNeighbor.GetLinkInfo().GetLinkQuality(); + + aNeighbor.GetLinkInfo().AddRss(aRxFrame.GetRssi()); + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE + aNeighbor.AggregateLinkMetrics(/* aSeriesId */ 0, aRxFrame.GetType(), aRxFrame.GetLqi(), aRxFrame.GetRssi()); +#endif + + // Signal when `aNeighbor` is the current parent and its link + // quality gets changed. + + VerifyOrExit(Get().IsChild() && (&aNeighbor == &Get().GetParent())); + VerifyOrExit(aNeighbor.GetLinkInfo().GetLinkQuality() != oldLinkQuality); + Get().Signal(kEventParentLinkQualityChanged); + +exit: + return; +} + bool Mac::HandleMacCommand(RxFrame &aFrame) { bool didHandle = false; diff --git a/src/core/mac/mac.hpp b/src/core/mac/mac.hpp index b0024f90a..26c9320eb 100644 --- a/src/core/mac/mac.hpp +++ b/src/core/mac/mac.hpp @@ -768,6 +768,7 @@ private: bool ShouldSendBeacon(void) const; bool IsJoinable(void) const; void BeginTransmit(void); + void UpdateNeighborLinkInfo(Neighbor &aNeighbor, const RxFrame &aRxFrame); bool HandleMacCommand(RxFrame &aFrame); void HandleTimer(void); diff --git a/tests/toranj/cli/cli.py b/tests/toranj/cli/cli.py index ee496d646..a086c42ec 100644 --- a/tests/toranj/cli/cli.py +++ b/tests/toranj/cli/cli.py @@ -713,6 +713,30 @@ class Node(object): if (instance_name == service['instance'] and service_name == service['name']): return service + #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + # br + + def br_init(self, if_inex, is_running): + self._cli_no_output('br init', if_inex, is_running) + + def br_enable(self): + self._cli_no_output('br enable') + + def br_disable(self): + self._cli_no_output('br disable') + + def br_get_state(self): + return self._cli_single_output('br state') + + def br_get_routeprf(self): + return self._cli_single_output('br routeprf') + + def br_set_routeprf(self, prf): + self._cli_no_output('br routeprf', prf) + + def br_clear_routeprf(self): + self._cli_no_output('br routeprf clear') + # ------------------------------------------------------------------------------------------------------------------ # Helper methods diff --git a/tests/toranj/cli/test-021-br-route-prf.py b/tests/toranj/cli/test-021-br-route-prf.py new file mode 100755 index 000000000..a58158b85 --- /dev/null +++ b/tests/toranj/cli/test-021-br-route-prf.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2023, 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 published route preference. +# +# + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print('-' * 120) +print('Starting \'{}\''.format(test_name)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Creating `cli.Node` instances + +speedup = 40 +cli.Node.set_time_speedup_factor(speedup) + +leader = cli.Node() +br = cli.Node() + +# ----------------------------------------------------------------------------------------------------------------------- +# Form topology + +leader.set_macfilter_lqi_to_node(br, 2) +br.set_macfilter_lqi_to_node(leader, 2) + +leader.form('br-route-prf') +br.join(leader) + +verify(leader.get_state() == 'leader') +verify(br.get_state() == 'router') + +# ----------------------------------------------------------------------------------------------------------------------- +# Test Implementation + +verify(br.br_get_state() == 'uninitialized') + +br.br_init(1, 1) + +verify(br.br_get_state() == 'disabled') + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Check the default route preference while BR is disabled + +verify(br.br_get_routeprf() == 'med') + +br.br_set_routeprf('low') +verify(br.br_get_routeprf() == 'low') + +br.br_set_routeprf('med') +verify(br.br_get_routeprf() == 'med') + +br.br_set_routeprf('high') +verify(br.br_get_routeprf() == 'high') + +br.br_clear_routeprf() +verify(br.br_get_routeprf() == 'med') + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Enable BR and check the published route and its preference + +br.br_enable() + + +def check_published_route_1(): + verify(br.br_get_state() == 'running') + routes = br.get_netdata_routes() + verify(len(routes) == 1) + verify(routes[0].startswith('fc00::/7 s med')) + verify(br.br_get_routeprf() == 'med') + + +verify_within(check_published_route_1, 5) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Force `br` to become an end-device. Check that the published +# route is now using `low` (since link quality to parent is +# configured as 2) + +br.set_router_eligible('disable') + +parent_info = br.get_parent_info() +verify(parent_info['Link Quality In'] == '2') + + +def check_published_route_2(): + verify(br.get_state() == 'child') + routes = br.get_netdata_routes() + verify(len(routes) == 1) + verify(routes[0].startswith('fc00::/7 s low')) + verify(br.br_get_routeprf() == 'low') + + +verify_within(check_published_route_2, 5) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Manually set the route prf to 'high` and validated that network +# data gets updated. Repeat setting route prf to `med`. + +br.br_set_routeprf('high') + + +def check_published_route_3(): + verify(br.get_state() == 'child') + routes = br.get_netdata_routes() + verify(len(routes) == 1) + verify(routes[0].startswith('fc00::/7 s high')) + verify(br.br_get_routeprf() == 'high') + + +verify_within(check_published_route_3, 5) + +br.br_set_routeprf('med') + + +def check_published_route_4(): + verify(br.get_state() == 'child') + routes = br.get_netdata_routes() + verify(len(routes) == 1) + verify(routes[0].startswith('fc00::/7 s med')) + verify(br.br_get_routeprf() == 'med') + + +verify_within(check_published_route_4, 5) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Clear the manually set route prf and validate that we go back +# to `low`. + +br.br_clear_routeprf() +verify(br.br_get_routeprf() == 'low') + + +def check_published_route_5(): + verify(br.get_state() == 'child') + routes = br.get_netdata_routes() + verify(len(routes) == 1) + verify(routes[0].startswith('fc00::/7 s low')) + verify(br.br_get_routeprf() == 'low') + + +verify_within(check_published_route_5, 5) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Allow `br` to take `router` role, validate that the route +# prf of `med` is used. + +br.set_router_eligible('enable') + + +def check_published_route_6(): + verify(br.get_state() == 'router') + routes = br.get_netdata_routes() + verify(len(routes) == 1) + verify(routes[0].startswith('fc00::/7 s med')) + verify(br.br_get_routeprf() == 'med') + + +verify_within(check_published_route_6, 15) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Update RSS MAC filter to use link quality 3 between `br` and +# `leader`. Again force `br` to become `child` and validate +# that now it still continues to use `med` prf. + +leader.set_macfilter_lqi_to_node(br, 3) +br.set_macfilter_lqi_to_node(leader, 3) + +br.set_router_eligible('disable') + + +def check_published_route_7(): + verify(br.get_state() == 'child') + routes = br.get_netdata_routes() + verify(len(routes) == 1) + verify(routes[0].startswith('fc00::/7 s med')) + verify(br.br_get_routeprf() == 'med') + + +verify_within(check_published_route_7, 5) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Update RSS MAC filter to use link quality 1 between `br` and +# `leader`. Validate that route prf is updated quickly to `low`. + +br.set_macfilter_lqi_to_node(leader, 1) +br.ping(leader.get_mleid_ip_addr()) +parent_info = br.get_parent_info() +verify(parent_info['Link Quality In'] == '1') + + +def check_published_route_8(): + verify(br.get_state() == 'child') + routes = br.get_netdata_routes() + verify(len(routes) == 1) + verify(routes[0].startswith('fc00::/7 s low')) + verify(br.br_get_routeprf() == 'low') + + +verify_within(check_published_route_8, 15) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Update RSS MAC filter to go back to link quality 3. +# Make sure the published route prf is not immediately +# updated and only updated after ~5 minutes. + +br.set_macfilter_lqi_to_node(leader, 3) +br.ping(leader.get_mleid_ip_addr()) +parent_info = br.get_parent_info() +verify(parent_info['Link Quality In'] == '3') + +verify(br.br_get_routeprf() == 'low') + + +def check_published_route_9(): + verify(br.get_state() == 'child') + routes = br.get_netdata_routes() + verify(len(routes) == 1) + verify(routes[0].startswith('fc00::/7 s med')) + verify(br.br_get_routeprf() == 'med') + + +# Wait for 5 minutes + +verify_within(check_published_route_9, (5 * 60 / speedup) + 5) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test finished + +cli.Node.finalize_all_nodes() + +print('\'{}\' passed.'.format(test_name)) diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index 550acd1f3..587f7557e 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -185,6 +185,7 @@ if [ "$TORANJ_CLI" = 1 ]; then run cli/test-018-next-hop-and-path-cost.py run cli/test-019-netdata-context-id.py run cli/test-020-net-diag-vendor-info.py + run cli/test-021-br-route-prf.py run cli/test-022-netdata-full.py run cli/test-400-srp-client-server.py run cli/test-601-channel-manager-channel-change.py