From ca8b29859d213a89d22248b6d21b25bb9435c544 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Wed, 3 Sep 2025 13:54:17 -0700 Subject: [PATCH] [routing-manager] track and expose addresses used on infra-if (#11891) This commit introduces a mechanism within the `RoutingManager` to track the IPv6 addresses used by the Border Router itself on the infrastructure interface, particularly when sending Router Advertisements. This provides visibility for debugging and monitoring purposes. A new data structure, `otBorderRoutingIfAddrEntry`, is added to represent an address and the time elapsed since it was last used as the source of an RA. The tracked addresses can be retrieved using the new public API `otBorderRoutingGetNextIfAddrEntry` or CLI command `br ifaddrs`. --- include/openthread/border_routing.h | 28 +++++- include/openthread/instance.h | 2 +- src/cli/README_BR.md | 17 ++++ src/cli/cli_br.cpp | 40 +++++++- src/core/api/border_routing_api.cpp | 10 ++ src/core/border_router/routing_manager.cpp | 97 ++++++++++++++++++- src/core/border_router/routing_manager.hpp | 57 +++++++++++ tests/toranj/cli/cli.py | 3 + .../toranj/cli/test-503-peer-tbr-discovery.py | 3 + 9 files changed, 250 insertions(+), 7 deletions(-) diff --git a/include/openthread/border_routing.h b/include/openthread/border_routing.h index c6863ab24..c0589234e 100644 --- a/include/openthread/border_routing.h +++ b/include/openthread/border_routing.h @@ -154,6 +154,15 @@ typedef struct otBorderRoutingPeerBorderRouterEntry uint32_t mAge; ///< Seconds since the BR appeared in the Network Data. } otBorderRoutingPeerBorderRouterEntry; +/** + * Represents an infra-if IPv6 address entry (an address used by this BR itself on the AIL). + */ +typedef struct otBorderRoutingIfAddrEntry +{ + otIp6Address mAddress; ///< The IPv6 address. + uint32_t mSecSinceLastUse; ///< Seconds since the last RA was sent from this BR using this address. +} otBorderRoutingIfAddrEntry; + /** * Represents a group of data of platform-generated RA messages processed. */ @@ -696,7 +705,7 @@ void otBorderRoutingSetMultiAilCallback(otInstance *aInstanc * * @retval OT_ERROR_NONE Iterated to the next address entry, @p aEntry and @p aIterator are updated. * @retval OT_ERROR_NOT_FOUND No more entries in the table. - * @retval OT_ERROR_INVALID_ARSG The iterator is invalid (used to iterate over other entry types, e.g. prefix). + * @retval OT_ERROR_INVALID_ARGS The iterator is invalid (used to iterate over other entry types, e.g. prefix). */ otError otBorderRoutingGetNextRdnssAddrEntry(otInstance *aInstance, otBorderRoutingPrefixTableIterator *aIterator, @@ -737,6 +746,23 @@ void otBorderRoutingSetRdnssAddrCallback(otInstance *aInsta otBorderRoutingRdnssAddrCallback aCallback, void *aContext); +/** + * Iterates over the infrastructure interface address entries. + * + * These are addresses used by the BR itself, for example, when sending Router Advertisements. + * + * @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 address entry, @p aEntry and @p aIterator are updated. + * @retval OT_ERROR_NOT_FOUND No more entries in the table. + * @retval OT_ERROR_INVALID_ARGS The iterator is invalid (used to iterate over other entry types, e.g., prefix). + */ +otError otBorderRoutingGetNextIfAddrEntry(otInstance *aInstance, + otBorderRoutingPrefixTableIterator *aIterator, + otBorderRoutingIfAddrEntry *aEntry); + /** * Enables / Disables DHCPv6 Prefix Delegation. * diff --git a/include/openthread/instance.h b/include/openthread/instance.h index dd2ea0edd..64731cde1 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 (532) +#define OPENTHREAD_API_VERSION (533) /** * @addtogroup api-instance diff --git a/src/cli/README_BR.md b/src/cli/README_BR.md index 95dcb1d33..a9dc9c963 100644 --- a/src/cli/README_BR.md +++ b/src/cli/README_BR.md @@ -8,6 +8,7 @@ Usage : `br [command] ...` - [disable](#disable) - [enable](#enable) - [help](#help) +- [ifaddrs](#ifaddrs) - [init](#init) - [nat64prefix](#nat64prefix) - [omrprefix](#omrprefix) @@ -60,6 +61,22 @@ Initializes the Border Routing Manager on given infrastructure interface. Done ``` +### ifaddrs + +Usage: `br ifaddrs` + +Get the infrastructure interface addresses. These are addresses used by the BR itself, for example, when sending Router Advertisements. + +Info per entry: + +- IPv6 address. +- Seconds since the last RA was sent from this BR using this address. + +```bash +> br ifaddrs +fe80::896:228b:4ae0:8609, sec-since-use:15 +``` + ### infraif Usage: `br infraif` diff --git a/src/cli/cli_br.cpp b/src/cli/cli_br.cpp index ef3d637e8..b0ef862e0 100644 --- a/src/cli/cli_br.cpp +++ b/src/cli/cli_br.cpp @@ -767,6 +767,43 @@ exit: return error; } +/** + * @cli br ifaddrs + * @code + * br ifaddrs + * fe80::896:228b:4ae0:8609, sec-since-use:15 + * Done + * @endcode + * @par + * Get the infrastructure interface addresses. These are addresses used by the BR itself, for example, when sending + * Router Advertisements. + * Info per entry: + * - IPv6 address + * - Seconds since the last RA was sent from this BR using this address. + * @sa otBorderRoutingGetNextIfAddrEntry + */ +template <> otError Br::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + otBorderRoutingPrefixTableIterator iterator; + otBorderRoutingIfAddrEntry entry; + + VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + otBorderRoutingPrefixTableInitIterator(GetInstancePtr(), &iterator); + + while (otBorderRoutingGetNextIfAddrEntry(GetInstancePtr(), &iterator, &entry) == OT_ERROR_NONE) + { + char string[OT_IP6_ADDRESS_STRING_SIZE]; + + otIp6AddressToString(&entry.mAddress, string, sizeof(string)); + OutputLine("%s, sec-since-use:%lu", string, ToUlong(entry.mSecSinceLastUse)); + } + +exit: + return error; +} + #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE const char *Br::Dhcp6PdStateToString(otBorderRoutingDhcp6PdState aState) @@ -1114,7 +1151,8 @@ otError Br::Process(Arg aArgs[]) #if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE CmdEntry("counters"), #endif - CmdEntry("disable"), CmdEntry("enable"), CmdEntry("infraif"), CmdEntry("init"), + CmdEntry("disable"), CmdEntry("enable"), CmdEntry("ifaddrs"), CmdEntry("infraif"), + CmdEntry("init"), #if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE CmdEntry("multiail"), #endif diff --git a/src/core/api/border_routing_api.cpp b/src/core/api/border_routing_api.cpp index da5c70c02..7371884cc 100644 --- a/src/core/api/border_routing_api.cpp +++ b/src/core/api/border_routing_api.cpp @@ -249,6 +249,16 @@ void otBorderRoutingSetRdnssAddrCallback(otInstance *aInsta AsCoreType(aInstance).Get().SetRdnssAddrCallback(aCallback, aContext); } +otError otBorderRoutingGetNextIfAddrEntry(otInstance *aInstance, + otBorderRoutingPrefixTableIterator *aIterator, + otBorderRoutingIfAddrEntry *aEntry) +{ + AssertPointerIsNotNull(aIterator); + AssertPointerIsNotNull(aEntry); + + return AsCoreType(aInstance).Get().GetNextIfAddrEntry(*aIterator, *aEntry); +} + #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE otError otBorderRoutingGetNextPeerBrEntry(otInstance *aInstance, diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index 5ab1bdabf..b535a8c7c 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -1092,6 +1092,26 @@ void RoutingManager::RdnssAddress::CopyInfoTo(RdnssAddrEntry &aEntry, TimeMilli aEntry.mLifetime = GetLifetime(); } +//--------------------------------------------------------------------------------------------------------------------- +// IfAddress + +void RoutingManager::IfAddress::SetFrom(const Ip6::Address &aAddress, uint32_t aUptimeNow) +{ + mAddress = aAddress; + mLastUseUptime = aUptimeNow; +} + +bool RoutingManager::IfAddress::Matches(const InvalidChecker &aChecker) const +{ + return !aChecker.Get().HasAddress(mAddress); +} + +void RoutingManager::IfAddress::CopyInfoTo(IfAddrEntry &aEntry, uint32_t aUptimeNow) const +{ + aEntry.mAddress = mAddress; + aEntry.mSecSinceLastUse = aUptimeNow - mLastUseUptime; +} + //--------------------------------------------------------------------------------------------------------------------- // NetDataPeerBrTracker @@ -1284,6 +1304,7 @@ void RoutingManager::RxRaTracker::Start(void) { HandleNetDataChange(); } void RoutingManager::RxRaTracker::Stop(void) { mRouters.Free(); + mIfAddresses.Free(); mLocalRaHeader.Clear(); mDecisionFactors.Clear(); @@ -1301,6 +1322,16 @@ void RoutingManager::RxRaTracker::ProcessRouterAdvertMessage(const RouterAdvert: Router *router; + switch (aRaOrigin) + { + case kThisBrOtherEntity: + case kThisBrRoutingManager: + UpdateIfAddresses(aSrcAddress); + break; + case kAnotherRouter: + break; + } + VerifyOrExit(aRaOrigin != kThisBrRoutingManager); router = mRouters.FindMatching(aSrcAddress); @@ -1600,6 +1631,27 @@ exit: } } +void RoutingManager::RxRaTracker::UpdateIfAddresses(const Ip6::Address &aAddress) +{ + Entry *entry; + + mIfAddresses.RemoveAndFreeAllMatching(IfAddress::InvalidChecker(GetInstance())); + + entry = mIfAddresses.FindMatching(aAddress); + + if (entry == nullptr) + { + entry = AllocateEntry(); + VerifyOrExit(entry != nullptr); + mIfAddresses.Push(*entry); + } + + entry->SetFrom(aAddress, Get().GetUptimeInSeconds()); + +exit: + return; +} + #if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE template <> @@ -1618,8 +1670,8 @@ exit: template RoutingManager::RxRaTracker::Entry *RoutingManager::RxRaTracker::AllocateEntry(void) { static_assert(TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue || - TypeTraits::IsSame::kValue, - "Type MSUT be either RoutePrefix, OnLinkPrefix, or RdnssAddress"); + TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue, + "Type MUST be RoutePrefix, OnLinkPrefix, RdnssAddress, or IfAddress"); Entry *entry = nullptr; SharedEntry *sharedEntry = mEntryPool.Allocate(); @@ -1643,8 +1695,8 @@ template <> void RoutingManager::RxRaTracker::Entry void RoutingManager::RxRaTracker::Entry::Free(void) { static_assert(TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue || - TypeTraits::IsSame::kValue, - "Type MSUT be either RoutePrefix, OnLinkPrefix, or RdnssAddress"); + TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue, + "Type MUST be RoutePrefix, OnLinkPrefix, RdnssAddress, or IfAddress"); Get().mRxRaTracker.mEntryPool.Free(*reinterpret_cast(this)); } @@ -2206,6 +2258,21 @@ exit: return error; } +Error RoutingManager::RxRaTracker::GetNextIfAddr(PrefixTableIterator &aIterator, IfAddrEntry &aEntry) const +{ + Error error = kErrorNone; + Iterator &iterator = static_cast(aIterator); + + ClearAllBytes(aEntry); + + SuccessOrExit(error = iterator.AdvanceToNextIfAddrEntry(mIfAddresses.GetHead())); + + iterator.GetEntry()->CopyInfoTo(aEntry, iterator.GetInitUptime()); + +exit: + return error; +} + #if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE uint16_t RoutingManager::RxRaTracker::CountReachablePeerBrs(void) const { @@ -2395,6 +2462,28 @@ exit: return error; } +Error RoutingManager::RxRaTracker::Iterator::AdvanceToNextIfAddrEntry(const Entry *aListHead) +{ + Error error = kErrorNone; + + if (GetType() == kUnspecified) + { + SetType(kIfAddrIterator); + SetEntry(aListHead); + } + else + { + VerifyOrExit(GetType() == kIfAddrIterator, error = kErrorInvalidArgs); + VerifyOrExit(HasEntry(), error = kErrorNotFound); + SetEntry(GetEntry()->GetNext()); + } + + VerifyOrExit(HasEntry(), error = kErrorNotFound); + +exit: + return error; +} + #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE Error RoutingManager::RxRaTracker::Iterator::AdvanceToNextPeerBr(const PeerBr *aPeerBrsHead) diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index f03d39a0f..6900737b9 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -104,6 +104,7 @@ public: typedef otBorderRoutingRouterEntry RouterEntry; ///< Router Entry. typedef otBorderRoutingRdnssAddrEntry RdnssAddrEntry; ///< RDNSS Address Entry. typedef otBorderRoutingRdnssAddrCallback RdnssAddrCallback; ///< RDNS Address changed callback. + typedef otBorderRoutingIfAddrEntry IfAddrEntry; ///< Infra-if IPv6 Address Entry. typedef otBorderRoutingPeerBorderRouterEntry PeerBrEntry; ///< Peer Border Router Entry. typedef otBorderRoutingPrefixTableEntry Dhcp6PdPrefix; ///< DHCPv6 PD prefix. typedef otPdProcessedRaInfo Dhcp6PdCounters; ///< DHCPv6 PD counters. @@ -552,6 +553,23 @@ public: mRxRaTracker.SetRdnssCallback(aCallback, aContext); } + /** + * Iterates over the infrastructure interface address entries. + * + * These are addresses used by the BR itself, for example, when sending Router Advertisements. + * + * @param[in,out] aIterator An iterator. + * @param[out] aEntry A reference to the entry to populate. + * + * @retval kErrorNone Iterated to the next address entry, @p aEntry and @p aIterator are updated. + * @retval kErrorNotFound No more entries in the table. + * @retval kErrorInvalidArgs The @p aIterator is not valid (e.g. used to iterate over other entry types). + */ + Error GetNextIfAddrEntry(PrefixTableIterator &aIterator, IfAddrEntry &aEntry) + { + return mRxRaTracker.GetNextIfAddr(aIterator, aEntry); + } + #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE /** @@ -907,6 +925,31 @@ private: //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + class IfAddress // An if-address used by this BR itself (e.g., sending RA). + { + public: + struct InvalidChecker : public InstanceLocator + { + // Used in `Matches()` to check if address is invalid `!Get().HasAddress(mAddress)`. + + explicit InvalidChecker(Instance &aInstance) + : InstanceLocator(aInstance) + { + } + }; + + void SetFrom(const Ip6::Address &aAddress, uint32_t aUptimeNow); + bool Matches(const Ip6::Address &aAddress) const { return (mAddress == aAddress); } + bool Matches(const InvalidChecker &aChecker) const; + void CopyInfoTo(IfAddrEntry &aEntry, uint32_t aUptimeNow) const; + + private: + Ip6::Address mAddress; + uint32_t mLastUseUptime; + }; + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE class RxRaTracker; @@ -1059,6 +1102,7 @@ private: Error GetNextEntry(PrefixTableIterator &aIterator, PrefixTableEntry &aEntry) const; Error GetNextRouter(PrefixTableIterator &aIterator, RouterEntry &aEntry) const; Error GetNextRdnssAddr(PrefixTableIterator &aIterator, RdnssAddrEntry &aEntry) const; + Error GetNextIfAddr(PrefixTableIterator &aIterator, IfAddrEntry &aEntry) const; // Callbacks notifying of changes void RemoveOrDeprecateOldEntries(TimeMilli aTimeThreshold); @@ -1188,6 +1232,7 @@ private: kRouterIterator, kPrefixIterator, kRdnssAddrIterator, + kIfAddrIterator, kPeerBrIterator, }; @@ -1201,6 +1246,7 @@ private: Error AdvanceToNextRouter(Type aType); Error AdvanceToNextEntry(void); Error AdvanceToNextRdnssAddrEntry(void); + Error AdvanceToNextIfAddrEntry(const Entry *aListHead); uint32_t GetInitUptime(void) const { return mData0; } TimeMilli GetInitTime(void) const { return TimeMilli(mData1); } Type GetType(void) const { return static_cast(mData2); } @@ -1248,6 +1294,7 @@ private: Entry mOnLinkEntry; Entry mRouteEntry; Entry mRdnssAddrEntry; + Entry mIfAddrEntry; }; #endif @@ -1279,6 +1326,7 @@ private: void ProcessPrefixInfoOption(const PrefixInfoOption &aPio, Router &aRouter); void ProcessRouteInfoOption(const RouteInfoOption &aRio, Router &aRouter); void ProcessRecursiveDnsServerOption(const RecursiveDnsServerOption &aRdnss, Router &aRouter); + void UpdateIfAddresses(const Ip6::Address &aAddress); void Evaluate(void); void DetermineStaleTimeFor(const OnLinkPrefix &aPrefix, NextFireTime &aStaleTime); void DetermineStaleTimeFor(const RoutePrefix &aPrefix, NextFireTime &aStaleTime); @@ -1303,10 +1351,12 @@ private: using RouterTimer = TimerMilliIn; using RdnssAddrTimer = TimerMilliIn; using RouterList = OwningList>; + using IfAddressList = OwningList>; using RdnssCallback = Callback; DecisionFactors mDecisionFactors; RouterList mRouters; + IfAddressList mIfAddresses; ExpirationTimer mExpirationTimer; StaleTimer mStaleTimer; RouterTimer mRouterTimer; @@ -1909,6 +1959,13 @@ inline RoutingManager::RxRaTracker::Entry &Routing return mRdnssAddrEntry; } +template <> +inline RoutingManager::RxRaTracker::Entry &RoutingManager::RxRaTracker::SharedEntry:: + GetEntry(void) +{ + return mIfAddrEntry; +} + // Declare template (full) specializations for `Router` type. template <> diff --git a/tests/toranj/cli/cli.py b/tests/toranj/cli/cli.py index 7be26e2c7..ec70f507d 100644 --- a/tests/toranj/cli/cli.py +++ b/tests/toranj/cli/cli.py @@ -854,6 +854,9 @@ class Node(object): def br_get_multiail(self): return self._cli_single_output('br multiail') + def br_get_ifaddrs(self): + return self.cli('br ifaddrs') + #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # trel diff --git a/tests/toranj/cli/test-503-peer-tbr-discovery.py b/tests/toranj/cli/test-503-peer-tbr-discovery.py index 2cc58d42f..2f2b668f5 100755 --- a/tests/toranj/cli/test-503-peer-tbr-discovery.py +++ b/tests/toranj/cli/test-503-peer-tbr-discovery.py @@ -119,6 +119,9 @@ for br in all_brs: for other_br in other_brs: rloc16 = other_br.get_rloc16() verify(any([rloc16 in peer for peer in peers])) + ifaddrs = br.br_get_ifaddrs() + verify(len(ifaddrs) == 1) + verify(ifaddrs[0].startswith('fe80:')) # Disable BR3 and validate that BR1 and BR2 detect this. # BR3 itself should continue to detect BR1 and BR2