From b09b7916833c2493400202d5883b9c38c410335c Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Fri, 8 Aug 2025 22:54:09 -0700 Subject: [PATCH] [history-tracker] track favored OMR prefix on a border router (#11775) This commit adds support for tracking the history of the favored OMR prefix on a border router. A new public API and the corresponding `history omrprefix` CLI command are added to retrieve the recorded history. A new `otHistoryTrackerFavoredOmrPrefix` type is introduced to represent an entry in the history. Each entry includes the OMR prefix, its preference, and a boolean flag `mIsLocal` indicating whether the prefix is the same as the one maintained locally by the border router. The local OMR prefix can be based on either a randomly generated ULA or a prefix delegated via DHCPv6-PD. --- include/openthread/history_tracker.h | 32 ++++++++ include/openthread/instance.h | 2 +- src/cli/README_HISTORY.md | 35 +++++++++ src/cli/cli_history.cpp | 86 +++++++++++++++++++++- src/core/api/history_tracker_api.cpp | 13 ++++ src/core/border_router/routing_manager.cpp | 5 ++ src/core/config/history_tracker.h | 11 +++ src/core/utils/history_tracker.cpp | 23 ++++++ src/core/utils/history_tracker.hpp | 23 ++++++ 9 files changed, 226 insertions(+), 4 deletions(-) diff --git a/include/openthread/history_tracker.h b/include/openthread/history_tracker.h index a96f192d3..48680d793 100644 --- a/include/openthread/history_tracker.h +++ b/include/openthread/history_tracker.h @@ -289,6 +289,19 @@ typedef enum OT_HISTORY_TRACKER_BORDER_AGENT_EPSKC_EVENT_DEACTIVATED_UNKNOWN, ///< Deactivated for an unknown reason. } otHistoryTrackerBorderAgentEpskcEvent; +/** + * Represents a favored OMR prefix tracked by a device acting as a Border Router (BR). + * + * The `mIsLocal` field indicates whether the favored OMR prefix is the same as the local one maintained by this BR. + * The local OMR prefix can be either based on (random) ULA or a prefix delegated via DHCPv6-PD. + */ +typedef struct otHistoryTrackerFavoredOmrPrefix +{ + otIp6Prefix mOmrPrefix; ///< The OMR prefix. + signed int mPreference : 2; ///< The 2-bit signed preference (`OT_ROUTE_PREFERENCE_*` values). + bool mIsLocal : 1; ///< `true` if the prefix is the local OMR prefix; `false` otherwise. +} otHistoryTrackerFavoredOmrPrefix; + /** * Initializes an `otHistoryTrackerIterator`. * @@ -484,6 +497,25 @@ const otHistoryTrackerBorderAgentEpskcEvent *otHistoryTrackerIterateBorderAgentE otHistoryTrackerIterator *aIterator, uint32_t *aEntryAge); +/** + * Iterates over the entries in the favored OMR prefix history list. + * + * Requires `OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE` (device acting as Border Router). + * + * @param[in] aInstance A pointer to the OpenThread instance. + * @param[in,out] aIterator A pointer to an iterator. MUST be initialized or the behavior is undefined. + * @param[out] aEntryAge A pointer to a variable to output the entry's age. MUST NOT be NULL. + * Age is provided as the duration (in milliseconds) from when entry was recorded to + * @p aIterator initialization time. It is set to `OT_HISTORY_TRACKER_MAX_AGE` for entries + * older than max age. + * + * @returns The `otHistoryTrackerFavoredOmrPrefix` entry or `NULL` if no more entries in the list. + */ +const otHistoryTrackerFavoredOmrPrefix *otHistoryTrackerIterateFavoredOmrPrefixHistory( + otInstance *aInstance, + otHistoryTrackerIterator *aIterator, + uint32_t *aEntryAge); + /** * Converts a given entry age to a human-readable string. * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 95120fdef..a87dd1b9d 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (523) +#define OPENTHREAD_API_VERSION (524) /** * @addtogroup api-instance diff --git a/src/cli/README_HISTORY.md b/src/cli/README_HISTORY.md index fc46cedc8..bdc4518d3 100644 --- a/src/cli/README_HISTORY.md +++ b/src/cli/README_HISTORY.md @@ -16,6 +16,7 @@ Usage : `history [command] ...` - [ipmaddr](#ipmaddr) - [neighbor](#neighbor) - [netinfo](#netinfo) +- [omrprefix](#omrprefix) - [prefix](#prefix) - [route](#route) - [router](#router) @@ -292,6 +293,40 @@ Print only the latest 2 entries. Done ``` +### omrprefix + +Usage `history omrprefix [list] []` + +Requires `OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE`. + +Print the favored OMR prefix history. Each entry provides: + +- The favored OMR prefix. +- Preference (`high`, `med`, `low`). +- IsLocal as boolean `yes`/`no` indicating whether the favored OMR prefix is the same as the local one maintained by this BR. + +Print the OMR prefix history as a table. + +```bash +> history omrprefix +| Age | OMR Prefix | Pref |IsLocal | ++----------------------+--------------------------------------------------+--------+--------+ +| 00:00:10.110 | fd44:dc78:510b:1::/64 | low | yes | +| 00:06:17.604 | 2001:1a:12d5:23ae::/64 | med | no | +| 00:13:11.235 | fd44:dc78:510b:1::/64 | low | yes | +Done +``` + +Print the OMR prefix history as a list + +```bash +> history omrprefix list +00:03:20.379 -> omr-prefix:fd44:dc78:510b:1::/64 prf:low is-local:yes +00:09:27.873 -> omr-prefix:2001:1a:12d5:23ae::/64 prf:med is-local:no +00:16:21.504 -> omr-prefix:fd44:dc78:510b:1::/64 prf:low is-local:yes +Done +``` + ### prefix Usage `history prefix [list] []` diff --git a/src/cli/cli_history.cpp b/src/cli/cli_history.cpp index 1bb887e7e..f3e395a83 100644 --- a/src/cli/cli_history.cpp +++ b/src/cli/cli_history.cpp @@ -1539,6 +1539,83 @@ exit: return error; } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + +/** + * @cli history omrprefix + * @code + * history omrprefix + * | Age | OMR Prefix | Pref |IsLocal | + * +----------------------+--------------------------------------------------+--------+--------+ + * | 00:00:10.110 | fd44:dc78:510b:1::/64 | low | yes | + * | 00:06:17.604 | 2001:1a:12d5:23ae::/64 | med | no | + * | 00:13:11.235 | fd44:dc78:510b:1::/64 | low | yes | + * Done + * @endcode + * @code + * history omrprefix list + * 00:03:20.379 -> omr-prefix:fd44:dc78:510b:1::/64 prf:low is-local:yes + * 00:09:27.873 -> omr-prefix:2001:1a:12d5:23ae::/64 prf:med is-local:no + * 00:16:21.504 -> omr-prefix:fd44:dc78:510b:1::/64 prf:low is-local:yes + * Done + * @endcode + * @cparam history omrprefix [@ca{list}] [@ca{num-entries}] + * * Use the `list` option to display the output in list format. Otherwise, the output is shown in table format. + * * Use the `num-entries` option to limit the output to the number of most-recent entries specified. If this option + * is not used, all stored entries are shown in the output. + * @par + * Displays the favored OMR prefix history in table or list format. + * @par + * Each table or list entry provides: + * * Age: Time elapsed since the command was issued, and given in the format: + * `hours`:`minutes`:`seconds`:`milliseconds` + * * OMR Prefix: The favored OMR prefix. + * * Preference: The prefix preference associated with the OMR prefix (`low`, `med`, `high`). + * * IsLocal: Indicates whether the favored OMR prefix is the same as the local one maintained by this BR. + * @sa otHistoryTrackerIterateFavoredOmrPrefixHistory + */ +template <> otError History::Process(Arg aArgs[]) +{ + otError error; + bool isList; + uint16_t numEntries; + otHistoryTrackerIterator iterator; + const otHistoryTrackerFavoredOmrPrefix *info; + uint32_t entryAge; + char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE]; + char prefixString[OT_IP6_PREFIX_STRING_SIZE]; + + SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries)); + + if (!isList) + { + static const char *const kTitles[] = {"Age", "OMR Prefix", "Pref", "IsLocal"}; + static const uint8_t kColumnWidths[] = {22, 50, 8, 8}; + + OutputTableHeader(kTitles, kColumnWidths); + } + + otHistoryTrackerInitIterator(&iterator); + + for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++) + { + info = otHistoryTrackerIterateFavoredOmrPrefixHistory(GetInstancePtr(), &iterator, &entryAge); + VerifyOrExit(info != nullptr); + + otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString)); + + otIp6PrefixToString(&info->mOmrPrefix, prefixString, sizeof(prefixString)); + + OutputLine(isList ? "%s -> omr-prefix:%s prf:%s is-local:%s" : "| %20s | %-48s | %-6s | %-6s |", ageString, + prefixString, PreferenceToString(info->mPreference), info->mIsLocal ? "yes" : "no"); + } + +exit: + return error; +} + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + const char *History::DnsSrpAddrTypeToString(otHistoryTrackerDnsSrpAddrType aType) { static const char *const kAddrTypeStrings[] = { @@ -1559,9 +1636,12 @@ otError History::Process(Arg aArgs[]) #define CmdEntry(aCommandString) {aCommandString, &History::Process} static constexpr Command kCommands[] = { - CmdEntry("dnssrpaddr"), CmdEntry("ipaddr"), CmdEntry("ipmaddr"), CmdEntry("neighbor"), - CmdEntry("netinfo"), CmdEntry("prefix"), CmdEntry("route"), CmdEntry("router"), - CmdEntry("rx"), CmdEntry("rxtx"), CmdEntry("tx"), + CmdEntry("dnssrpaddr"), CmdEntry("ipaddr"), CmdEntry("ipmaddr"), CmdEntry("neighbor"), CmdEntry("netinfo"), +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + CmdEntry("omrprefix"), +#endif + CmdEntry("prefix"), CmdEntry("route"), CmdEntry("router"), CmdEntry("rx"), CmdEntry("rxtx"), + CmdEntry("tx"), }; #undef CmdEntry diff --git a/src/core/api/history_tracker_api.cpp b/src/core/api/history_tracker_api.cpp index 51f7aac8a..2d99695e3 100644 --- a/src/core/api/history_tracker_api.cpp +++ b/src/core/api/history_tracker_api.cpp @@ -152,6 +152,19 @@ const otHistoryTrackerBorderAgentEpskcEvent *otHistoryTrackerIterateBorderAgentE } #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +const otHistoryTrackerFavoredOmrPrefix *otHistoryTrackerIterateFavoredOmrPrefixHistory( + otInstance *aInstance, + otHistoryTrackerIterator *aIterator, + uint32_t *aEntryAge) +{ + AssertPointerIsNotNull(aEntryAge); + + return AsCoreType(aInstance).Get().IterateFavoredOmrPrefixHistory(AsCoreType(aIterator), + *aEntryAge); +} +#endif + void otHistoryTrackerEntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_t aSize) { HistoryTracker::Local::EntryAgeToString(aEntryAge, aBuffer, aSize); diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index d42f24b74..058816a6d 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -2649,6 +2649,11 @@ void RoutingManager::OmrPrefixManager::SetFavoredPrefix(const OmrPrefix &aOmrPre #endif LogInfo("Favored OMR prefix: %s -> %s", FavoredToString(oldFavoredPrefix).AsCString(), FavoredToString(mFavoredPrefix).AsCString()); + +#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE + Get().RecordFavoredOmrPrefix(mFavoredPrefix.GetPrefix(), mFavoredPrefix.GetPreference(), + mFavoredPrefix.GetPrefix() == mLocalPrefix.GetPrefix()); +#endif } } diff --git a/src/core/config/history_tracker.h b/src/core/config/history_tracker.h index 090c975d3..76a7f19f1 100644 --- a/src/core/config/history_tracker.h +++ b/src/core/config/history_tracker.h @@ -182,6 +182,17 @@ #define OPENTHREAD_CONFIG_HISTORY_TRACKER_EPSKC_EVENT_SIZE 64 #endif +/** + * @def OPENTHREAD_CONFIG_HISTORY_TRACKER_OMR_PREFIX_LIST_SIZE + * + * Specifies the maximum number of entries in OMR Prefix history. + * + * Can be set to zero to configure History Tracker module not to collect any OMR prefix info. + */ +#ifndef OPENTHREAD_CONFIG_HISTORY_TRACKER_OMR_PREFIX_LIST_SIZE +#define OPENTHREAD_CONFIG_HISTORY_TRACKER_OMR_PREFIX_LIST_SIZE 16 +#endif + /** * @} */ diff --git a/src/core/utils/history_tracker.cpp b/src/core/utils/history_tracker.cpp index 153d2ea52..1a7ce2fc3 100644 --- a/src/core/utils/history_tracker.cpp +++ b/src/core/utils/history_tracker.cpp @@ -577,6 +577,26 @@ exit: } #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + +void Local::RecordFavoredOmrPrefix(const Ip6::Prefix &aPrefix, + BorderRouter::RoutingManager::RoutePreference aPreference, + bool aIsLocal) +{ + FavoredOmrPrefix *entry = mFavoredOmrPrefixHistory.AddNewEntry(); + + VerifyOrExit(entry != nullptr); + + entry->mOmrPrefix = aPrefix; + entry->mPreference = NetworkData::RoutePreferenceToValue(aPreference); + entry->mIsLocal = aIsLocal; + +exit: + return; +} + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + void Local::HandleNotifierEvents(Events aEvents) { if (aEvents.ContainsAny(kEventThreadRoleChanged | kEventThreadRlocAdded | kEventThreadRlocRemoved | @@ -606,6 +626,9 @@ void Local::HandleTimer(void) mDnsSrpAddrHistory.UpdateAgedEntries(); #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE mEpskcEventHistory.UpdateAgedEntries(); +#endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + mFavoredOmrPrefixHistory.UpdateAgedEntries(); #endif mTimer.Start(kAgeCheckPeriod); } diff --git a/src/core/utils/history_tracker.hpp b/src/core/utils/history_tracker.hpp index 27c894391..a43ceaf80 100644 --- a/src/core/utils/history_tracker.hpp +++ b/src/core/utils/history_tracker.hpp @@ -41,6 +41,7 @@ #include #include +#include "border_router/routing_manager.hpp" #include "common/as_core_type.hpp" #include "common/clearable.hpp" #include "common/locator.hpp" @@ -109,6 +110,9 @@ typedef otHistoryTrackerDnsSrpAddrInfo DnsSrpAddrInfo; ///< Network #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE typedef otHistoryTrackerBorderAgentEpskcEvent EpskcEvent; ///< Border Agent ePSKc Event. #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +typedef otHistoryTrackerFavoredOmrPrefix FavoredOmrPrefix; ///< Favored OMR Prefix +#endif /** * This constant specifies the maximum age of entries which is 49 days (value in msec). @@ -146,6 +150,9 @@ class Local : public InstanceLocator, private NonCopyable friend class ot::MeshCoP::BorderAgent; friend class ot::MeshCoP::BorderAgent::EphemeralKeyManager; #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + friend class ot::BorderRouter::RoutingManager; +#endif public: /** @@ -267,6 +274,13 @@ public: } #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + const FavoredOmrPrefix *IterateFavoredOmrPrefixHistory(Iterator &aIterator, uint32_t &aEntryAge) const + { + return mFavoredOmrPrefixHistory.Iterate(aIterator, aEntryAge); + } +#endif + /** * Converts a given entry age to a human-readable string. * @@ -300,6 +314,7 @@ private: static constexpr uint16_t kExternalRouteListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_EXTERNAL_ROUTE_LIST_SIZE; static constexpr uint16_t kDnsSrpAddrListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_DNSSRP_ADDR_LIST_SIZE; static constexpr uint16_t kEpskcEventListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_EPSKC_EVENT_SIZE; + static constexpr uint16_t kOmrPrefixListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_OMR_PREFIX_LIST_SIZE; typedef otHistoryTrackerAddressEvent AddressEvent; @@ -477,6 +492,11 @@ private: #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE void RecordEpskcEvent(EpskcEvent aEvent); #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + void RecordFavoredOmrPrefix(const Ip6::Prefix &aPrefix, + BorderRouter::RoutingManager::RoutePreference aPreference, + bool aIsLocal); +#endif using TrackerTimer = TimerMilliIn; @@ -493,6 +513,9 @@ private: #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE EntryList mEpskcEventHistory; #endif +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + EntryList mFavoredOmrPrefixHistory; +#endif TrackerTimer mTimer;