diff --git a/include/openthread/history_tracker.h b/include/openthread/history_tracker.h index c502cf751..a3d09a772 100644 --- a/include/openthread/history_tracker.h +++ b/include/openthread/history_tracker.h @@ -314,6 +314,49 @@ typedef struct otHistoryTrackerFavoredOnLinkPrefix bool mIsLocal : 1; ///< `true` if the prefix is the local on-link prefix; `false` otherwise. } otHistoryTrackerFavoredOnLinkPrefix; +/** + * Defines events for discovered routers on an Adjacent Infrastructure Link (AIL). + * + * This applies when a device is acting as a Border Router, processing received Router Advertisements and tracking + * AIL routers. + * + * `OT_HISTORY_TRACKER_AIL_ROUTER_EVENT_CHANGED` is used if any of the properties in the `otHistoryTrackerAilRouter` + * structure associated with a specific router changes. + */ +typedef enum +{ + OT_HISTORY_TRACKER_AIL_ROUTER_EVENT_ADDED = 0, ///< A new AIL router is discovered. + OT_HISTORY_TRACKER_AIL_ROUTER_EVENT_CHANGED = 1, ///< A property in the router's information has changed. + OT_HISTORY_TRACKER_AIL_ROUTER_EVENT_REMOVED = 2, ///< The AIL router is removed and no longer tracked. +} otHistoryTrackerAilRouterEvent; + +/** + * Represents information about a discovered router on an Adjacent Infrastructure Link (AIL). + * + * This applies when a device is acting as a Border Router, processing received Router Advertisements and tracking + * information about discovered AIL routers. + * + * `mProvidesDefaultRoute` indicates whether the router provides a default route. If it does, `mDefRoutePreference` + * specifies the route preference. + * + * `mFavoredOnLinkPrefix` indicates the favored on-link prefix advertised by the router. If there is no on-link prefix, + * this will be an empty prefix (i.e., its length will be zero). + */ +typedef struct otHistoryTrackerAilRouter +{ + otHistoryTrackerAilRouterEvent mEvent; ///< The event type (e.g., added, changed, removed). + int8_t mDefRoutePreference; ///< Def route preference. + otIp6Address mAddress; ///< The IPv6 address of the AIL router. + otIp6Prefix mFavoredOnLinkPrefix; ///< The favored on-link prefix, if any. + bool mProvidesDefaultRoute : 1; ///< Whether the router provides a default route. + bool mManagedAddressConfigFlag : 1; ///< The Managed Address Config flag (`M` flag). + bool mOtherConfigFlag : 1; ///< The Other Config flag (`O` flag). + bool mSnacRouterFlag : 1; ///< The SNAC Router flag (`S` flag). + bool mIsLocalDevice : 1; ///< This router is the local device (this BR). + bool mIsReachable : 1; ///< This router is reachable. + bool mIsPeerBr : 1; ///< This router is (likely) a peer BR. +} otHistoryTrackerAilRouter; + /** * Initializes an `otHistoryTrackerIterator`. * @@ -547,6 +590,24 @@ const otHistoryTrackerFavoredOnLinkPrefix *otHistoryTrackerIterateFavoredOnLinkP otHistoryTrackerIterator *aIterator, uint32_t *aEntryAge); +/** + * Iterates over the entries in the BR AIL routers 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 `otHistoryTrackerAilRouter` entry or `NULL` if no more entries in the list. + */ +const otHistoryTrackerAilRouter *otHistoryTrackerIterateAilRoutersHistory(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 9bb574fe9..4ac85ddd7 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 (527) +#define OPENTHREAD_API_VERSION (528) /** * @addtogroup api-instance diff --git a/src/cli/README_HISTORY.md b/src/cli/README_HISTORY.md index 50b9697f5..d82d53b89 100644 --- a/src/cli/README_HISTORY.md +++ b/src/cli/README_HISTORY.md @@ -11,6 +11,7 @@ The number of entries recorded for each history list is configurable through a s Usage : `history [command] ...` - [help](#help) +- [ailrouters](#ailrouters) - [dnssrpaddr](#dnssrpaddr) - [ipaddr](#ipaddr) - [ipmaddr](#ipmaddr) @@ -68,6 +69,55 @@ Done > ``` +### ailrouters + +Usage `history ailrouters [list] []` + +Requires `OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE`. + +Print the AIL routers history. Each entry provides: + +- The event, possible values are `Added`, `Changed`, `Removed`. +- The IPv6 address of the AIL router. +- Flags + - `R`: Indicates whether or not this router is reachable. + - `M`: The Managed Address Config flag (from the received Router Advertisement (RA) header). + - `O`: The Other Config flag (from the RA header). + - `S`: The SNAC Router flag (from the RA header). + - `L`: Indicates whether or not this router is the local device (this BR). + - `P`: Indicates whether or not this router is a peer BR connected to same Thread mesh. + - `D`: Indicates whether or not this router provides a default route. +- The default route preference (`high`, `med`, `low`) if this router provides a default route. Otherwise `-`. +- The favored on-link prefix advertised by this router if any. If none, `-` is shown. + +```bash +> history ailrouters +| Age | Event | IPv6 Address |R|M|O|S|L|P|D|RtPrf | ++----------------------+---------+------------------------------------------+-+-+-+-+-+-+-+------+ +| | Favored on-link prefix | | | | | | | | | ++----------------------+----------------------------------------------------+-+-+-+-+-+-+-+------+ +| 00:01:35.107 | Changed | fe80:0:0:0:0:0:0:2 |N|N|N|Y|N|Y|N| | +| | - | | | | | | | | | +| 00:05:01.115 | Changed | fe80:0:0:0:0:0:0:2 |Y|N|N|Y|N|Y|N| | +| | - | | | | | | | | | +| 00:08:01.804 | Added | fe80:0:0:0:0:0:0:2 |Y|N|N|Y|N|Y|N| | +| | fd99:4cdc:3b3d:56ef::/64 | | | | | | | | | +Done +``` + +Print the history as a list. + +```bash +>history ailrouters list +00:01:35.161 -> event:Changed address:fe80:0:0:0:0:0:0:2 reachable:N M:N O:N S:Y local:N peer:Y def-route:N prf: + favored-on-link-prefix:- +00:05:01.169 -> event:Changed address:fe80:0:0:0:0:0:0:2 reachable:Y M:N O:N S:Y local:N peer:Y def-route:N prf: + favored-on-link-prefix:- +00:08:01.858 -> event:Added address:fe80:0:0:0:0:0:0:2 reachable:Y M:N O:N S:Y local:N peer:Y def-route:N prf: + favored-on-link-prefix:fd99:4cdc:3b3d:56ef::/64 +Done +``` + ### dnssrpaddr Usage `history dnssrpaddr [list] []` diff --git a/src/cli/cli_history.cpp b/src/cli/cli_history.cpp index 142ce4eab..c2788aba8 100644 --- a/src/cli/cli_history.cpp +++ b/src/cli/cli_history.cpp @@ -1684,6 +1684,126 @@ exit: return error; } +/** + * @cli history ailrouters + * @code + * history ailrouters + * | Age | Event | IPv6 Address |R|M|O|S|L|P|D|RtPrf | + * +----------------------+---------+------------------------------------------+-+-+-+-+-+-+-+------+ + * | | Favored on-link prefix | | | | | | | | | + * +----------------------+----------------------------------------------------+-+-+-+-+-+-+-+------+ + * | 00:01:35.107 | Changed | fe80:0:0:0:0:0:0:2 |N|N|N|Y|N|Y|N| | + * | | - | | | | | | | | | + * | 00:05:01.115 | Changed | fe80:0:0:0:0:0:0:2 |Y|N|N|Y|N|Y|N| | + * | | - | | | | | | | | | + * | 00:08:01.804 | Added | fe80:0:0:0:0:0:0:2 |Y|N|N|Y|N|Y|N| | + * | | fd99:4cdc:3b3d:56ef::/64 | | | | | | | | | + * Done + * @endcode + * @code + * history ailrouters list + * 00:01:35.161 -> event:Changed address:fe80:0:0:0:0:0:0:2 reachable:N M:N O:N S:Y local:N peer:Y def-route:N prf: + * favored-on-link-prefix:- + * 00:05:01.169 -> event:Changed address:fe80:0:0:0:0:0:0:2 reachable:Y M:N O:N S:Y local:N peer:Y def-route:N prf: + * favored-on-link-prefix:- + * 00:08:01.858 -> event:Added address:fe80:0:0:0:0:0:0:2 reachable:Y M:N O:N S:Y local:N peer:Y def-route:N prf: + * favored-on-link-prefix:fd99:4cdc:3b3d:56ef::/64 + * Done + * @endcode + * @cparam history ailrouters [@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 AIL routers 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` + * * The event, possible values are `Added`, `Changed`, `Removed`. + * * The IPv6 address of the AIL router. + * * Flags + * * `R`: Indicates whether or not this router is reachable. + * * `M`: The Managed Address Config flag (from the Router Advertisement (RA) header). + * * `O`: The Other Config flag (from the RA header). + * * `S`: The SNAC Router flag (from the RA header). + * * `L`: Indicates whether or not this router is the local device (this BR). + * * `P`: Indicates whether or not this router is a peer BR connected to same Thread mesh. + * * `D`: Indicates whether or not this router provides a default route. + * * The default route preference (`high`, `med`, `low`) if this router provides a default route, otherwise `-`. + * * The favored on-link prefix advertised by this router if any. If none, `-` is shown. + * @sa otHistoryTrackerIterateAilRoutersHistory + */ +template <> otError History::Process(Arg aArgs[]) +{ + otError error; + bool isList; + uint16_t numEntries; + otHistoryTrackerIterator iterator; + const otHistoryTrackerAilRouter *info; + uint32_t entryAge; + char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE]; + char addrString[OT_IP6_ADDRESS_STRING_SIZE]; + char prefixString[OT_IP6_PREFIX_STRING_SIZE]; + + SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries)); + + if (!isList) + { + static const char *const kTitles1[] = {"Age", "Event", "IPv6 Address", "R", "M", "O", "S", "L", + "P", "D", "RtPrf"}; + static const uint8_t kColWidths1[] = {22, 9, 42, 1, 1, 1, 1, 1, 1, 1, 6}; + + static const char *const kTitles2[] = {"", "Favored on-link prefix", "", "", "", "", "", "", "", ""}; + static const uint8_t kColWidths2[] = {22, 52, 1, 1, 1, 1, 1, 1, 1, 6}; + + OutputTableHeader(kTitles1, kColWidths1); + OutputTableHeader(kTitles2, kColWidths2); + } + + otHistoryTrackerInitIterator(&iterator); + + for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++) + { + info = otHistoryTrackerIterateAilRoutersHistory(GetInstancePtr(), &iterator, &entryAge); + VerifyOrExit(info != nullptr); + + otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString)); + + otIp6AddressToString(&info->mAddress, addrString, sizeof(addrString)); + otIp6PrefixToString(&info->mFavoredOnLinkPrefix, prefixString, sizeof(prefixString)); + + OutputLine(isList ? "%s -> event:%s address:%s reachable:%c M:%c O:%c S:%c local:%c peer:%c def-route:%c prf:%s" + : "| %20s | %-7s | %-40s |%c|%c|%c|%c|%c|%c|%c| %4s |", + ageString, AilRouterEventToString(info->mEvent), addrString, info->mIsReachable ? 'Y' : 'N', + info->mManagedAddressConfigFlag ? 'Y' : 'N', info->mOtherConfigFlag ? 'Y' : 'N', + info->mSnacRouterFlag ? 'Y' : 'N', info->mIsLocalDevice ? 'Y' : 'N', info->mIsPeerBr ? 'Y' : 'N', + info->mProvidesDefaultRoute ? 'Y' : 'N', + info->mProvidesDefaultRoute ? PreferenceToString(info->mDefRoutePreference) : "-"); + + OutputLine(isList ? " %sfavored-on-link-prefix:%s" : "| %20s | %-50s | | | | | | | | |", "", + info->mFavoredOnLinkPrefix.mLength == 0 ? "-" : prefixString); + } + +exit: + return error; +} + +const char *History::AilRouterEventToString(otHistoryTrackerAilRouterEvent aEvent) +{ + static const char *const kAilRouterEventStrings[] = { + "Added", /* (0) OT_HISTORY_TRACKER_AIL_ROUTER_EVENT_ADDED */ + "Changed", /* (1) OT_HISTORY_TRACKER_AIL_ROUTER_EVENT_CHANGED */ + "Removed", /* (2) OT_HISTORY_TRACKER_AIL_ROUTER_EVENT_REMOVED */ + }; + + static_assert(OT_HISTORY_TRACKER_AIL_ROUTER_EVENT_ADDED == 0, "AIL_ROUTER_EVENT_ADDED is incorrect"); + static_assert(OT_HISTORY_TRACKER_AIL_ROUTER_EVENT_CHANGED == 1, "AIL_ROUTER_EVENT_CHANGED is incorrect"); + static_assert(OT_HISTORY_TRACKER_AIL_ROUTER_EVENT_REMOVED == 2, "AIL_ROUTER_EVENT_REMOVED is incorrect"); + + return Stringify(aEvent, kAilRouterEventStrings, "--"); +} + #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE const char *History::DnsSrpAddrTypeToString(otHistoryTrackerDnsSrpAddrType aType) @@ -1706,6 +1826,9 @@ otError History::Process(Arg aArgs[]) #define CmdEntry(aCommandString) {aCommandString, &History::Process} static constexpr Command kCommands[] = { +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + CmdEntry("ailrouters"), +#endif CmdEntry("dnssrpaddr"), CmdEntry("ipaddr"), CmdEntry("ipmaddr"), CmdEntry("neighbor"), CmdEntry("netinfo"), #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE diff --git a/src/cli/cli_history.hpp b/src/cli/cli_history.hpp index 0ae8e3a0d..a37918e10 100644 --- a/src/cli/cli_history.hpp +++ b/src/cli/cli_history.hpp @@ -101,6 +101,7 @@ private: static const char *RadioTypeToString(const otHistoryTrackerMessageInfo &aInfo); static const char *MessageTypeToString(const otHistoryTrackerMessageInfo &aInfo); static const char *DnsSrpAddrTypeToString(otHistoryTrackerDnsSrpAddrType aType); + static const char *AilRouterEventToString(otHistoryTrackerAilRouterEvent aEvent); }; } // namespace Cli diff --git a/src/core/api/history_tracker_api.cpp b/src/core/api/history_tracker_api.cpp index fee7e68ba..6b66bf9a4 100644 --- a/src/core/api/history_tracker_api.cpp +++ b/src/core/api/history_tracker_api.cpp @@ -176,7 +176,17 @@ const otHistoryTrackerFavoredOnLinkPrefix *otHistoryTrackerIterateFavoredOnLinkP *aEntryAge); } -#endif +const otHistoryTrackerAilRouter *otHistoryTrackerIterateAilRoutersHistory(otInstance *aInstance, + otHistoryTrackerIterator *aIterator, + uint32_t *aEntryAge) +{ + AssertPointerIsNotNull(aEntryAge); + + return AsCoreType(aInstance).Get().IterateAilRoutersHistory(AsCoreType(aIterator), + *aEntryAge); +} + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE 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 fa7941ad9..f3ca7dc19 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -1767,6 +1767,7 @@ void RoutingManager::RxRaTracker::Evaluate(void) NextFireTime entryExpireTime(now); NextFireTime staleTime(now); NextFireTime rdnsssAddrExpireTime(now); + RouterList removedRouters; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Remove expired entries associated with each router @@ -1788,7 +1789,16 @@ void RoutingManager::RxRaTracker::Evaluate(void) // Remove any router entry that no longer has any valid on-link // or route prefixes, RDNSS addresses, or other relevant flags set. - mRouters.RemoveAndFreeAllMatching(Router::EmptyChecker()); + mRouters.RemoveAllMatching(removedRouters, Router::EmptyChecker()); + +#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE + for (Router &router : removedRouters) + { + ReportChangesToHistoryTracker(router, /* aRemoved */ true); + } +#endif + + removedRouters.Free(); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Determine decision factors (favored on-link prefix, has any @@ -1891,6 +1901,17 @@ void RoutingManager::RxRaTracker::Evaluate(void) mExpirationTimer.FireAt(entryExpireTime); mStaleTimer.FireAt(staleTime); mRdnssAddrTimer.FireAt(rdnsssAddrExpireTime); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Report any changes to history tracker. + +#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE + for (Router &router : mRouters) + { + ReportChangesToHistoryTracker(router, /* aRemoved */ false); + } + +#endif } void RoutingManager::RxRaTracker::DetermineStaleTimeFor(const OnLinkPrefix &aPrefix, NextFireTime &aStaleTime) @@ -2202,6 +2223,62 @@ uint16_t RoutingManager::RxRaTracker::CountReachablePeerBrs(void) const } #endif +#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE + +void RoutingManager::RxRaTracker::ReportChangesToHistoryTracker(Router &aRouter, bool aRemoved) +{ + // Report any changes in the `aRouter` to `HistoryTracker` only if + // something has changed since the last recorded event. + + Router::HistoryInfo oldInfo; + HistoryTracker::AilRouter *entry; + + if (aRemoved) + { + // If we have never recorded this router entry in the + // `HistoryTracker`, there is no point in reporting its + // removal. This can happen if we receive an RA from a router + // with no useful information that we want to track. In this + // case, the router entry is removed immediately during + // `Evaluate()`. + + VerifyOrExit(aRouter.mHistoryInfo.mHistoryRecorded); + } + else + { + oldInfo = aRouter.mHistoryInfo; + } + + aRouter.mHistoryInfo.DetermineFrom(aRouter); + + VerifyOrExit(aRemoved || (aRouter.mHistoryInfo != oldInfo)); + + // Allocate and populate the new `HistoryTracker::AilRouter` entry. + + entry = Get().RecordAilRouterEvent(); + VerifyOrExit(entry != nullptr); + + entry->mEvent = aRemoved ? HistoryTracker::Local::kAilRouterRemoved + : (oldInfo.mHistoryRecorded ? HistoryTracker::Local::kAilRouterChanged + : HistoryTracker::Local::kAilRouterAdded); + + entry->mAddress = aRouter.mAddress; + entry->mDefRoutePreference = static_cast(aRouter.mHistoryInfo.mDefRoutePreference); + entry->mFavoredOnLinkPrefix = aRouter.mHistoryInfo.mFavoredOnLinkPrefix; + entry->mProvidesDefaultRoute = aRouter.mHistoryInfo.mProvidesDefaultRoute; + entry->mManagedAddressConfigFlag = aRouter.mHistoryInfo.mManagedAddressConfigFlag; + entry->mOtherConfigFlag = aRouter.mHistoryInfo.mOtherConfigFlag; + entry->mSnacRouterFlag = aRouter.mHistoryInfo.mSnacRouterFlag; + entry->mIsLocalDevice = aRouter.mHistoryInfo.mIsLocalDevice; + entry->mIsReachable = aRouter.mHistoryInfo.mIsReachable; + entry->mIsPeerBr = aRouter.mHistoryInfo.mIsPeerBr; + +exit: + return; +} + +#endif // OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE + //--------------------------------------------------------------------------------------------------------------------- // RxRaTracker::Iterator @@ -2430,6 +2507,46 @@ void RoutingManager::RxRaTracker::Router::CopyInfoTo(RouterEntry &aEntry, TimeMi aEntry.mIsPeerBr = IsPeerBr(); } +//--------------------------------------------------------------------------------------------------------------------- +// RxRaTracker::Router::HistoryInfo + +#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE + +void RoutingManager::RxRaTracker::Router::HistoryInfo::DetermineFrom(const Router &aRouter) +{ + Ip6::Prefix emptyPrefix; + const RoutePrefix *defRoute; + + Clear(); + + mHistoryRecorded = true; + mManagedAddressConfigFlag = aRouter.mManagedAddressConfigFlag; + mOtherConfigFlag = aRouter.mOtherConfigFlag; + mSnacRouterFlag = aRouter.mSnacRouterFlag; + mIsLocalDevice = aRouter.mIsLocalDevice; + mIsReachable = aRouter.IsReachable(); + mIsPeerBr = aRouter.IsPeerBr(); + + emptyPrefix.Clear(); + defRoute = aRouter.mRoutePrefixes.FindMatching(emptyPrefix); + + if ((defRoute != nullptr) && (defRoute->GetValidLifetime() > 0)) + { + mProvidesDefaultRoute = true; + mDefRoutePreference = defRoute->GetRoutePreference(); + } + + for (const OnLinkPrefix &onLinkPrefix : aRouter.mOnLinkPrefixes) + { + if (onLinkPrefix.IsFavoredOver(mFavoredOnLinkPrefix)) + { + mFavoredOnLinkPrefix = onLinkPrefix.GetPrefix(); + } + } +} + +#endif // OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE + //--------------------------------------------------------------------------------------------------------------------- // RxRaTracker::DecisionFactors diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index 7b15d8bbf..0d2e8de45 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -1123,6 +1123,24 @@ private: { }; +#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE + struct HistoryInfo : public Equatable, public Clearable + { + void DetermineFrom(const Router &aRouter); + + bool mHistoryRecorded : 1; + bool mManagedAddressConfigFlag : 1; + bool mOtherConfigFlag : 1; + bool mSnacRouterFlag : 1; + bool mIsLocalDevice : 1; + bool mIsReachable : 1; + bool mIsPeerBr : 1; + bool mProvidesDefaultRoute : 1; + RoutePreference mDefRoutePreference; + Ip6::Prefix mFavoredOnLinkPrefix; + }; +#endif + bool IsReachable(void) const { return mNsProbeCount <= kMaxNsProbes; } bool ShouldCheckReachability(void) const; void ResetReachabilityState(void); @@ -1159,6 +1177,9 @@ private: bool mSnacRouterFlag : 1; bool mIsLocalDevice : 1; bool mAllEntriesDisregarded : 1; +#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE + HistoryInfo mHistoryInfo; +#endif }; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1270,6 +1291,10 @@ private: #if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE uint16_t CountReachablePeerBrs(void) const; #endif +#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE + void ReportChangesToHistoryTracker(Router &aRouter, bool aRemoved); +#endif + #if OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE template Entry *AllocateEntry(void) { return Entry::Allocate(); } #else diff --git a/src/core/config/history_tracker.h b/src/core/config/history_tracker.h index 48b6d4f17..b2520de23 100644 --- a/src/core/config/history_tracker.h +++ b/src/core/config/history_tracker.h @@ -204,6 +204,17 @@ #define OPENTHREAD_CONFIG_HISTORY_TRACKER_ON_LINK_PREFIX_LIST_SIZE 16 #endif +/** + * @def OPENTHREAD_CONFIG_HISTORY_TRACKER_AIL_ROUTER_LIST_SIZE + * + * Specifies the maximum number of entries in BR AIL Router history. + * + * Can be set to zero to configure History Tracker module not to collect any BR AIL Router info. + */ +#ifndef OPENTHREAD_CONFIG_HISTORY_TRACKER_AIL_ROUTER_LIST_SIZE +#define OPENTHREAD_CONFIG_HISTORY_TRACKER_AIL_ROUTER_LIST_SIZE 32 +#endif + /** * @} */ diff --git a/src/core/utils/history_tracker.cpp b/src/core/utils/history_tracker.cpp index a1e5723bc..8396033a3 100644 --- a/src/core/utils/history_tracker.cpp +++ b/src/core/utils/history_tracker.cpp @@ -608,6 +608,8 @@ exit: return; } +AilRouter *Local::RecordAilRouterEvent(void) { return mAilRoutersHistory.AddNewEntry(); } + #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE void Local::HandleNotifierEvents(Events aEvents) @@ -643,6 +645,7 @@ void Local::HandleTimer(void) #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE mFavoredOmrPrefixHistory.UpdateAgedEntries(); mFavoredOnLinkPrefixHistory.UpdateAgedEntries(); + mAilRoutersHistory.UpdateAgedEntries(); #endif mTimer.Start(kAgeCheckPeriod); } diff --git a/src/core/utils/history_tracker.hpp b/src/core/utils/history_tracker.hpp index 478862b39..0e2dae7db 100644 --- a/src/core/utils/history_tracker.hpp +++ b/src/core/utils/history_tracker.hpp @@ -113,6 +113,7 @@ typedef otHistoryTrackerBorderAgentEpskcEvent EpskcEvent; ///< Border Agent ePSK #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE typedef otHistoryTrackerFavoredOmrPrefix FavoredOmrPrefix; ///< Favored OMR Prefix typedef otHistoryTrackerFavoredOnLinkPrefix FavoredOnLinkPrefix; ///< Favored On-link Prefix +typedef otHistoryTrackerAilRouter AilRouter; ///< An AIL router tracked when acting as BR #endif /** @@ -285,6 +286,11 @@ public: { return mFavoredOnLinkPrefixHistory.Iterate(aIterator, aEntryAge); } + + const AilRouter *IterateAilRoutersHistory(Iterator &aIterator, uint32_t &aEntryAge) const + { + return mAilRoutersHistory.Iterate(aIterator, aEntryAge); + } #endif /** @@ -322,6 +328,7 @@ private: 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; + static constexpr uint16_t kAilRouterListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_AIL_ROUTER_LIST_SIZE; typedef otHistoryTrackerAddressEvent AddressEvent; @@ -357,6 +364,14 @@ private: static constexpr uint16_t kAnycastServerPort = 53; +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + typedef otHistoryTrackerAilRouterEvent AilRouterEvent; + + static constexpr AilRouterEvent kAilRouterAdded = OT_HISTORY_TRACKER_AIL_ROUTER_EVENT_ADDED; + static constexpr AilRouterEvent kAilRouterChanged = OT_HISTORY_TRACKER_AIL_ROUTER_EVENT_CHANGED; + static constexpr AilRouterEvent kAilRouterRemoved = OT_HISTORY_TRACKER_AIL_ROUTER_EVENT_REMOVED; +#endif + #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE && OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE #define DefineEpskcEvent(aName, aPublicEnumName) \ static constexpr EpskcEvent kEpskc##aName = OT_HISTORY_TRACKER_BORDER_AGENT_EPSKC_EVENT_##aPublicEnumName @@ -500,10 +515,11 @@ private: void RecordEpskcEvent(EpskcEvent aEvent); #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE - void RecordFavoredOmrPrefix(const Ip6::Prefix &aPrefix, - BorderRouter::RoutingManager::RoutePreference aPreference, - bool aIsLocal); - void RecordFavoredOnLinkPrefix(const Ip6::Prefix &aPrefix, bool aIsLocal); + void RecordFavoredOmrPrefix(const Ip6::Prefix &aPrefix, + BorderRouter::RoutingManager::RoutePreference aPreference, + bool aIsLocal); + void RecordFavoredOnLinkPrefix(const Ip6::Prefix &aPrefix, bool aIsLocal); + AilRouter *RecordAilRouterEvent(void); #endif using TrackerTimer = TimerMilliIn; @@ -524,6 +540,7 @@ private: #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE EntryList mFavoredOmrPrefixHistory; EntryList mFavoredOnLinkPrefixHistory; + EntryList mAilRoutersHistory; #endif TrackerTimer mTimer; diff --git a/tests/toranj/cli/test-501-multi-br-failure-recovery.py b/tests/toranj/cli/test-501-multi-br-failure-recovery.py index 338c955c5..476a2095b 100755 --- a/tests/toranj/cli/test-501-multi-br-failure-recovery.py +++ b/tests/toranj/cli/test-501-multi-br-failure-recovery.py @@ -203,6 +203,18 @@ verify(len(services) == len(nodes_non_br)) for host in hosts: verify(host['addresses'][0].startswith(br2_local_omr[:-4])) +# Check the history tracker `ailrouters` command + +hist_table = br2.cli('history ailrouters') +verify(len(hist_table) == 10) + +hist_list = br2.cli('history ailrouters list') +verify(len(hist_list) == 6) + +verify(hist_list[0].strip().split(' -> ')[1].startswith('event:Changed ')) +verify(hist_list[2].strip().split(' -> ')[1].startswith('event:Changed ')) +verify(hist_list[4].strip().split(' -> ')[1].startswith('event:Added ')) + # ----------------------------------------------------------------------------------------------------------------------- # Test finished