[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`.
This commit is contained in:
Abtin Keshavarzian
2025-09-03 13:54:17 -07:00
committed by GitHub
parent 4cb0de4233
commit ca8b29859d
9 changed files with 250 additions and 7 deletions
+27 -1
View File
@@ -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.
*
+1 -1
View File
@@ -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
+17
View File
@@ -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`
+39 -1
View File
@@ -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<Cmd("ifaddrs")>(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
+10
View File
@@ -249,6 +249,16 @@ void otBorderRoutingSetRdnssAddrCallback(otInstance *aInsta
AsCoreType(aInstance).Get<BorderRouter::RoutingManager>().SetRdnssAddrCallback(aCallback, aContext);
}
otError otBorderRoutingGetNextIfAddrEntry(otInstance *aInstance,
otBorderRoutingPrefixTableIterator *aIterator,
otBorderRoutingIfAddrEntry *aEntry)
{
AssertPointerIsNotNull(aIterator);
AssertPointerIsNotNull(aEntry);
return AsCoreType(aInstance).Get<BorderRouter::RoutingManager>().GetNextIfAddrEntry(*aIterator, *aEntry);
}
#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE
otError otBorderRoutingGetNextPeerBrEntry(otInstance *aInstance,
+93 -4
View File
@@ -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<InfraIf>().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<IfAddress> *entry;
mIfAddresses.RemoveAndFreeAllMatching(IfAddress::InvalidChecker(GetInstance()));
entry = mIfAddresses.FindMatching(aAddress);
if (entry == nullptr)
{
entry = AllocateEntry<IfAddress>();
VerifyOrExit(entry != nullptr);
mIfAddresses.Push(*entry);
}
entry->SetFrom(aAddress, Get<Uptime>().GetUptimeInSeconds());
exit:
return;
}
#if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE
template <>
@@ -1618,8 +1670,8 @@ exit:
template <class Type> RoutingManager::RxRaTracker::Entry<Type> *RoutingManager::RxRaTracker::AllocateEntry(void)
{
static_assert(TypeTraits::IsSame<Type, OnLinkPrefix>::kValue || TypeTraits::IsSame<Type, RoutePrefix>::kValue ||
TypeTraits::IsSame<Type, RdnssAddress>::kValue,
"Type MSUT be either RoutePrefix, OnLinkPrefix, or RdnssAddress");
TypeTraits::IsSame<Type, RdnssAddress>::kValue || TypeTraits::IsSame<Type, IfAddress>::kValue,
"Type MUST be RoutePrefix, OnLinkPrefix, RdnssAddress, or IfAddress");
Entry<Type> *entry = nullptr;
SharedEntry *sharedEntry = mEntryPool.Allocate();
@@ -1643,8 +1695,8 @@ template <> void RoutingManager::RxRaTracker::Entry<RoutingManager::RxRaTracker:
template <class Type> void RoutingManager::RxRaTracker::Entry<Type>::Free(void)
{
static_assert(TypeTraits::IsSame<Type, OnLinkPrefix>::kValue || TypeTraits::IsSame<Type, RoutePrefix>::kValue ||
TypeTraits::IsSame<Type, RdnssAddress>::kValue,
"Type MSUT be either RoutePrefix, OnLinkPrefix, or RdnssAddress");
TypeTraits::IsSame<Type, RdnssAddress>::kValue || TypeTraits::IsSame<Type, IfAddress>::kValue,
"Type MUST be RoutePrefix, OnLinkPrefix, RdnssAddress, or IfAddress");
Get<RoutingManager>().mRxRaTracker.mEntryPool.Free(*reinterpret_cast<SharedEntry *>(this));
}
@@ -2206,6 +2258,21 @@ exit:
return error;
}
Error RoutingManager::RxRaTracker::GetNextIfAddr(PrefixTableIterator &aIterator, IfAddrEntry &aEntry) const
{
Error error = kErrorNone;
Iterator &iterator = static_cast<Iterator &>(aIterator);
ClearAllBytes(aEntry);
SuccessOrExit(error = iterator.AdvanceToNextIfAddrEntry(mIfAddresses.GetHead()));
iterator.GetEntry<IfAddress>()->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<IfAddress> *aListHead)
{
Error error = kErrorNone;
if (GetType() == kUnspecified)
{
SetType(kIfAddrIterator);
SetEntry(aListHead);
}
else
{
VerifyOrExit(GetType() == kIfAddrIterator, error = kErrorInvalidArgs);
VerifyOrExit(HasEntry(), error = kErrorNotFound);
SetEntry(GetEntry<IfAddress>()->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)
@@ -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<InfraIf>().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<IfAddress> *aListHead);
uint32_t GetInitUptime(void) const { return mData0; }
TimeMilli GetInitTime(void) const { return TimeMilli(mData1); }
Type GetType(void) const { return static_cast<Type>(mData2); }
@@ -1248,6 +1294,7 @@ private:
Entry<OnLinkPrefix> mOnLinkEntry;
Entry<RoutePrefix> mRouteEntry;
Entry<RdnssAddress> mRdnssAddrEntry;
Entry<IfAddress> 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<RoutingManager, &RoutingManager::HandleRxRaTrackerRouterTimer>;
using RdnssAddrTimer = TimerMilliIn<RoutingManager, &RoutingManager::HandleRxRaTrackerRdnssAddrTimer>;
using RouterList = OwningList<Entry<Router>>;
using IfAddressList = OwningList<Entry<IfAddress>>;
using RdnssCallback = Callback<RdnssAddrCallback>;
DecisionFactors mDecisionFactors;
RouterList mRouters;
IfAddressList mIfAddresses;
ExpirationTimer mExpirationTimer;
StaleTimer mStaleTimer;
RouterTimer mRouterTimer;
@@ -1909,6 +1959,13 @@ inline RoutingManager::RxRaTracker::Entry<RoutingManager::RdnssAddress> &Routing
return mRdnssAddrEntry;
}
template <>
inline RoutingManager::RxRaTracker::Entry<RoutingManager::IfAddress> &RoutingManager::RxRaTracker::SharedEntry::
GetEntry(void)
{
return mIfAddrEntry;
}
// Declare template (full) specializations for `Router` type.
template <>
+3
View File
@@ -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
@@ -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