diff --git a/include/openthread/history_tracker.h b/include/openthread/history_tracker.h index 48680d793..c502cf751 100644 --- a/include/openthread/history_tracker.h +++ b/include/openthread/history_tracker.h @@ -302,6 +302,18 @@ typedef struct otHistoryTrackerFavoredOmrPrefix bool mIsLocal : 1; ///< `true` if the prefix is the local OMR prefix; `false` otherwise. } otHistoryTrackerFavoredOmrPrefix; +/** + * Represents a favored on-link prefix on AIL tracked by a device acting as a Border Router (BR). + * + * The `mIsLocal` field indicates whether the favored on-link prefix is the same as the local one maintained by this + * BR. + */ +typedef struct otHistoryTrackerFavoredOnLinkPrefix +{ + otIp6Prefix mOnLinkPrefix; ///< The on-link prefix. + bool mIsLocal : 1; ///< `true` if the prefix is the local on-link prefix; `false` otherwise. +} otHistoryTrackerFavoredOnLinkPrefix; + /** * Initializes an `otHistoryTrackerIterator`. * @@ -516,6 +528,25 @@ const otHistoryTrackerFavoredOmrPrefix *otHistoryTrackerIterateFavoredOmrPrefixH otHistoryTrackerIterator *aIterator, uint32_t *aEntryAge); +/** + * Iterates over the entries in the favored on-link 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 the entry was recorded to + * @p aIterator initialization time. It is set to `OT_HISTORY_TRACKER_MAX_AGE` for entries + * older than the max age. + * + * @returns The `otHistoryTrackerFavoredOnLinkPrefix` entry or `NULL` if no more entries in the list. + */ +const otHistoryTrackerFavoredOnLinkPrefix *otHistoryTrackerIterateFavoredOnLinkPrefixHistory( + 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 56f938da3..8a84368b6 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 (525) +#define OPENTHREAD_API_VERSION (526) /** * @addtogroup api-instance diff --git a/src/cli/README_HISTORY.md b/src/cli/README_HISTORY.md index bdc4518d3..50b9697f5 100644 --- a/src/cli/README_HISTORY.md +++ b/src/cli/README_HISTORY.md @@ -17,6 +17,7 @@ Usage : `history [command] ...` - [neighbor](#neighbor) - [netinfo](#netinfo) - [omrprefix](#omrprefix) +- [onlinkprefix](#onlinkprefix) - [prefix](#prefix) - [route](#route) - [router](#router) @@ -317,7 +318,7 @@ Print the OMR prefix history as a table. Done ``` -Print the OMR prefix history as a list +Print the OMR prefix history as a list. ```bash > history omrprefix list @@ -327,6 +328,37 @@ Print the OMR prefix history as a list Done ``` +### onlinkprefix + +Usage `history onlinkprefix [list] []` + +Requires `OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE`. + +Print the favored on-link prefix history. Each entry provides: + +- The favored on-link prefix (on AIL). +- IsLocal as boolean `yes`/`no` indicating whether the favored on-link prefix is the same as the local one maintained by this BR. + +Print the on-link prefix history as a table. + +```bash +> history onlinkprefix +| Age | On-link Prefix |IsLocal | ++----------------------+--------------------------------------------------+--------+ +| 00:00:50.600 | 2001:efc6:75a8:efee::/64 | no | +| 00:11:04.327 | fd74:fe69:9f21:437::/64 | yes | +Done +``` + +Print the on-link prefix history as a list. + +```bash +> history onlinkprefix list +00:00:50.600 -> on-link-prefix:2001:efc6:75a8:efee::/64 is-local:no +00:11:04.327 -> on-link-prefix:fd74:fe69:9f21:437::/64 is-local:yes +Done +``` + ### prefix Usage `history prefix [list] []` diff --git a/src/cli/cli_history.cpp b/src/cli/cli_history.cpp index f3e395a83..dde22f917 100644 --- a/src/cli/cli_history.cpp +++ b/src/cli/cli_history.cpp @@ -1614,6 +1614,76 @@ exit: return error; } +/** + * @cli history onlinkprefix + * @code + * history onlinkprefix + * | Age | On-link Prefix |IsLocal | + * +----------------------+--------------------------------------------------+--------+ + * | 00:00:50.600 | 2001:efc6:75a8:efee::/64 | no | + * | 00:11:04.327 | fd74:fe69:9f21:437::/64 | yes | + * Done + * @endcode + * @code + * history onlinkprefix list + * 00:00:50.600 -> on-link-prefix:2001:efc6:75a8:efee::/64 is-local:no + * 00:11:04.327 -> on-link-prefix:fd74:fe69:9f21:437::/64 is-local:yes + * Done + * @endcode + * @cparam history onlinkprefix [@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 on-link 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` + * * On-link Prefix: The favored on-link prefix on AIL. + * * IsLocal: Indicates whether the favored on-link prefix is the same as the local one maintained by this BR. + * @sa otHistoryTrackerIterateFavoredOnLinkPrefixHistory + */ +template <> otError History::Process(Arg aArgs[]) +{ + otError error; + bool isList; + uint16_t numEntries; + otHistoryTrackerIterator iterator; + const otHistoryTrackerFavoredOnLinkPrefix *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", "On-link Prefix", "IsLocal"}; + static const uint8_t kColumnWidths[] = {22, 50, 8}; + + OutputTableHeader(kTitles, kColumnWidths); + } + + otHistoryTrackerInitIterator(&iterator); + + for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++) + { + info = otHistoryTrackerIterateFavoredOnLinkPrefixHistory(GetInstancePtr(), &iterator, &entryAge); + VerifyOrExit(info != nullptr); + + otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString)); + + otIp6PrefixToString(&info->mOnLinkPrefix, prefixString, sizeof(prefixString)); + + OutputLine(isList ? "%s -> on-link-prefix:%s is-local:%s" : "| %20s | %-48s | %-6s |", ageString, prefixString, + info->mIsLocal ? "yes" : "no"); + } + +exit: + return error; +} + #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE const char *History::DnsSrpAddrTypeToString(otHistoryTrackerDnsSrpAddrType aType) @@ -1636,12 +1706,13 @@ 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("dnssrpaddr"), CmdEntry("ipaddr"), CmdEntry("ipmaddr"), + CmdEntry("neighbor"), CmdEntry("netinfo"), #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE - CmdEntry("omrprefix"), + CmdEntry("omrprefix"), CmdEntry("onlinkprefix"), #endif - CmdEntry("prefix"), CmdEntry("route"), CmdEntry("router"), CmdEntry("rx"), CmdEntry("rxtx"), - CmdEntry("tx"), + 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 2d99695e3..fee7e68ba 100644 --- a/src/core/api/history_tracker_api.cpp +++ b/src/core/api/history_tracker_api.cpp @@ -153,6 +153,7 @@ const otHistoryTrackerBorderAgentEpskcEvent *otHistoryTrackerIterateBorderAgentE #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + const otHistoryTrackerFavoredOmrPrefix *otHistoryTrackerIterateFavoredOmrPrefixHistory( otInstance *aInstance, otHistoryTrackerIterator *aIterator, @@ -163,6 +164,18 @@ const otHistoryTrackerFavoredOmrPrefix *otHistoryTrackerIterateFavoredOmrPrefixH return AsCoreType(aInstance).Get().IterateFavoredOmrPrefixHistory(AsCoreType(aIterator), *aEntryAge); } + +const otHistoryTrackerFavoredOnLinkPrefix *otHistoryTrackerIterateFavoredOnLinkPrefixHistory( + otInstance *aInstance, + otHistoryTrackerIterator *aIterator, + uint32_t *aEntryAge) +{ + AssertPointerIsNotNull(aEntryAge); + + return AsCoreType(aInstance).Get().IterateFavoredOnLinkPrefixHistory(AsCoreType(aIterator), + *aEntryAge); +} + #endif void otHistoryTrackerEntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_t aSize) diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index 64917ec4a..bdd22d685 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -3072,6 +3072,10 @@ void RoutingManager::OnLinkPrefixManager::SetFavoredPrefix(const Ip6::Prefix &aP mFavoredPrefix = aPrefix; +#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE + Get().RecordFavoredOnLinkPrefix(mFavoredPrefix, mFavoredPrefix == mLocalPrefix); +#endif + exit: return; } diff --git a/src/core/config/history_tracker.h b/src/core/config/history_tracker.h index 76a7f19f1..48b6d4f17 100644 --- a/src/core/config/history_tracker.h +++ b/src/core/config/history_tracker.h @@ -193,6 +193,17 @@ #define OPENTHREAD_CONFIG_HISTORY_TRACKER_OMR_PREFIX_LIST_SIZE 16 #endif +/** + * @def OPENTHREAD_CONFIG_HISTORY_TRACKER_ON_LINK_PREFIX_LIST_SIZE + * + * Specifies the maximum number of entries in On-Link Prefix (on AIL) history. + * + * Can be set to zero to configure History Tracker module not to collect any on-link prefix info. + */ +#ifndef OPENTHREAD_CONFIG_HISTORY_TRACKER_ON_LINK_PREFIX_LIST_SIZE +#define OPENTHREAD_CONFIG_HISTORY_TRACKER_ON_LINK_PREFIX_LIST_SIZE 16 +#endif + /** * @} */ diff --git a/src/core/utils/history_tracker.cpp b/src/core/utils/history_tracker.cpp index 254f3bf19..a1e5723bc 100644 --- a/src/core/utils/history_tracker.cpp +++ b/src/core/utils/history_tracker.cpp @@ -595,6 +595,19 @@ exit: return; } +void Local::RecordFavoredOnLinkPrefix(const Ip6::Prefix &aPrefix, bool aIsLocal) +{ + FavoredOnLinkPrefix *entry = mFavoredOnLinkPrefixHistory.AddNewEntry(); + + VerifyOrExit(entry != nullptr); + + entry->mOnLinkPrefix = aPrefix; + entry->mIsLocal = aIsLocal; + +exit: + return; +} + #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE void Local::HandleNotifierEvents(Events aEvents) @@ -629,6 +642,7 @@ void Local::HandleTimer(void) #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE mFavoredOmrPrefixHistory.UpdateAgedEntries(); + mFavoredOnLinkPrefixHistory.UpdateAgedEntries(); #endif mTimer.Start(kAgeCheckPeriod); } diff --git a/src/core/utils/history_tracker.hpp b/src/core/utils/history_tracker.hpp index a43ceaf80..478862b39 100644 --- a/src/core/utils/history_tracker.hpp +++ b/src/core/utils/history_tracker.hpp @@ -111,7 +111,8 @@ typedef otHistoryTrackerDnsSrpAddrInfo DnsSrpAddrInfo; ///< Network typedef otHistoryTrackerBorderAgentEpskcEvent EpskcEvent; ///< Border Agent ePSKc Event. #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE -typedef otHistoryTrackerFavoredOmrPrefix FavoredOmrPrefix; ///< Favored OMR Prefix +typedef otHistoryTrackerFavoredOmrPrefix FavoredOmrPrefix; ///< Favored OMR Prefix +typedef otHistoryTrackerFavoredOnLinkPrefix FavoredOnLinkPrefix; ///< Favored On-link Prefix #endif /** @@ -279,6 +280,11 @@ public: { return mFavoredOmrPrefixHistory.Iterate(aIterator, aEntryAge); } + + const FavoredOnLinkPrefix *IterateFavoredOnLinkPrefixHistory(Iterator &aIterator, uint32_t &aEntryAge) const + { + return mFavoredOnLinkPrefixHistory.Iterate(aIterator, aEntryAge); + } #endif /** @@ -315,6 +321,7 @@ private: 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; + static constexpr uint16_t kOnLinkPrefixListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_ON_LINK_PREFIX_LIST_SIZE; typedef otHistoryTrackerAddressEvent AddressEvent; @@ -496,6 +503,7 @@ private: void RecordFavoredOmrPrefix(const Ip6::Prefix &aPrefix, BorderRouter::RoutingManager::RoutePreference aPreference, bool aIsLocal); + void RecordFavoredOnLinkPrefix(const Ip6::Prefix &aPrefix, bool aIsLocal); #endif using TrackerTimer = TimerMilliIn; @@ -514,7 +522,8 @@ private: EntryList mEpskcEventHistory; #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE - EntryList mFavoredOmrPrefixHistory; + EntryList mFavoredOmrPrefixHistory; + EntryList mFavoredOnLinkPrefixHistory; #endif TrackerTimer mTimer; diff --git a/tests/toranj/cli/test-500-two-brs-two-networks.py b/tests/toranj/cli/test-500-two-brs-two-networks.py index 2c2d5a618..4d8157f42 100755 --- a/tests/toranj/cli/test-500-two-brs-two-networks.py +++ b/tests/toranj/cli/test-500-two-brs-two-networks.py @@ -100,13 +100,15 @@ verify(br1_favored_onlink == br2_favored_onlink) # Check that the two BRs discover and track each other (not as peer BR since # connected to different networks). -for br in [br1, br1]: +for br in [br1, br2]: routers = br.br_get_routers() verify(len(routers) > 0) for router in routers: verify('reachable:yes' in router) verify('S:1' in router) verify(not router.endswith('(peer BR)')) + hist_list = br.cli('history onlinkprefix list') + verify(len(hist_list) == 1) # ----------------------------------------------------------------------------------------------------------------------- # Test finished