diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 5e99aaa37..d8f08f00e 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -388,6 +388,8 @@ openthread_core_files = [ "border_router/infra_if.hpp", "border_router/routing_manager.cpp", "border_router/routing_manager.hpp", + "border_router/rx_ra_tracker.cpp", + "border_router/rx_ra_tracker.hpp", "coap/coap.cpp", "coap/coap.hpp", "coap/coap_message.cpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 257f17de0..590fb2e44 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -102,6 +102,7 @@ set(COMMON_SOURCES border_router/dhcp6_pd_client.cpp border_router/infra_if.cpp border_router/routing_manager.cpp + border_router/rx_ra_tracker.cpp coap/coap.cpp coap/coap_message.cpp coap/coap_secure.cpp diff --git a/src/core/api/border_routing_api.cpp b/src/core/api/border_routing_api.cpp index cac417d20..c5b064f16 100644 --- a/src/core/api/border_routing_api.cpp +++ b/src/core/api/border_routing_api.cpp @@ -208,7 +208,7 @@ void otBorderRoutingPrefixTableInitIterator(otInstance *aInstance, otBorderRouti { AssertPointerIsNotNull(aIterator); - AsCoreType(aInstance).Get().InitPrefixTableIterator(*aIterator); + AsCoreType(aInstance).Get().InitIterator(*aIterator); } otError otBorderRoutingGetNextPrefixTableEntry(otInstance *aInstance, @@ -218,7 +218,7 @@ otError otBorderRoutingGetNextPrefixTableEntry(otInstance AssertPointerIsNotNull(aIterator); AssertPointerIsNotNull(aEntry); - return AsCoreType(aInstance).Get().GetNextPrefixTableEntry(*aIterator, *aEntry); + return AsCoreType(aInstance).Get().GetNextPrefixTableEntry(*aIterator, *aEntry); } otError otBorderRoutingGetNextRouterEntry(otInstance *aInstance, @@ -228,7 +228,7 @@ otError otBorderRoutingGetNextRouterEntry(otInstance *aI AssertPointerIsNotNull(aIterator); AssertPointerIsNotNull(aEntry); - return AsCoreType(aInstance).Get().GetNextRouterEntry(*aIterator, *aEntry); + return AsCoreType(aInstance).Get().GetNextRouterEntry(*aIterator, *aEntry); } otError otBorderRoutingGetNextRdnssAddrEntry(otInstance *aInstance, @@ -238,14 +238,14 @@ otError otBorderRoutingGetNextRdnssAddrEntry(otInstance AssertPointerIsNotNull(aIterator); AssertPointerIsNotNull(aEntry); - return AsCoreType(aInstance).Get().GetNextRdnssAddrEntry(*aIterator, *aEntry); + return AsCoreType(aInstance).Get().GetNextRdnssAddrEntry(*aIterator, *aEntry); } void otBorderRoutingSetRdnssAddrCallback(otInstance *aInstance, otBorderRoutingRdnssAddrCallback aCallback, void *aContext) { - AsCoreType(aInstance).Get().SetRdnssAddrCallback(aCallback, aContext); + AsCoreType(aInstance).Get().SetRdnssAddrCallback(aCallback, aContext); } otError otBorderRoutingGetNextIfAddrEntry(otInstance *aInstance, @@ -255,7 +255,7 @@ otError otBorderRoutingGetNextIfAddrEntry(otInstance *aI AssertPointerIsNotNull(aIterator); AssertPointerIsNotNull(aEntry); - return AsCoreType(aInstance).Get().GetNextIfAddrEntry(*aIterator, *aEntry); + return AsCoreType(aInstance).Get().GetNextIfAddrEntry(*aIterator, *aEntry); } #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE diff --git a/src/core/border_router/br_log.hpp b/src/core/border_router/br_log.hpp index e015e34f6..f518d18b4 100644 --- a/src/core/border_router/br_log.hpp +++ b/src/core/border_router/br_log.hpp @@ -39,6 +39,7 @@ #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE #include "border_router/br_types.hpp" +#include "common/log.hpp" #include "net/ip6_address.hpp" namespace ot { @@ -85,10 +86,10 @@ void LogRecursiveDnsServerOption(const Ip6::Address &aAddress, uint32_t aLifetim #else -void LogRaHeader(const RouterAdvert::Header &) {} -void LogPrefixInfoOption(const Ip6::Prefix &, uint32_t, uint32_t, PrefixInfoOption::Flags) {} -void LogRouteInfoOption(const Ip6::Prefix &, uint32_t, RoutePreference) {} -void LogRecursiveDnsServerOption(const Ip6::Address &, uint32_t) {} +inline void LogRaHeader(const RouterAdvert::Header &) {} +inline void LogPrefixInfoOption(const Ip6::Prefix &, uint32_t, uint32_t, PrefixInfoOption::Flags) {} +inline void LogRouteInfoOption(const Ip6::Prefix &, uint32_t, RoutePreference) {} +inline void LogRecursiveDnsServerOption(const Ip6::Address &, uint32_t) {} #endif // OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) diff --git a/src/core/border_router/br_tracker.cpp b/src/core/border_router/br_tracker.cpp index c4b9f6636..5d87776f8 100644 --- a/src/core/border_router/br_tracker.cpp +++ b/src/core/border_router/br_tracker.cpp @@ -88,7 +88,7 @@ uint16_t NetDataBrTracker::CountBrs(Filter aFilter, uint32_t &aMinAge) const Error NetDataBrTracker::GetNext(Filter aFilter, TableIterator &aIterator, BorderRouterEntry &aEntry) const { - using Iterator = RoutingManager::RxRaTracker::Iterator; + using Iterator = RxRaTracker::Iterator; Iterator &iterator = static_cast(aIterator); Error error = kErrorNone; diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index d9a8e268d..33039c715 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -31,7 +31,7 @@ * This file includes implementation for the RA-based routing management. */ -#include "border_router/routing_manager.hpp" +#include "routing_manager.hpp" #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE @@ -62,7 +62,6 @@ RoutingManager::RoutingManager(Instance &aInstance) #if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE , mMultiAilDetector(aInstance) #endif - , mRxRaTracker(aInstance) , mRoutePublisher(aInstance) #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE , mNat64PrefixManager(aInstance) @@ -307,7 +306,7 @@ void RoutingManager::Start(void) LogInfo("Starting"); mIsRunning = true; - mRxRaTracker.Start(); + Get().Start(); mOnLinkPrefixManager.Start(); mOmrPrefixManager.Start(); mRoutePublisher.Start(); @@ -342,7 +341,7 @@ void RoutingManager::Stop(void) SendRouterAdvertisement(kInvalidateAllPrevPrefixes); - mRxRaTracker.Stop(); + Get().Stop(); mTxRaInfo.mTxCount = 0; @@ -447,7 +446,7 @@ void RoutingManager::HandleNotifierEvents(Events aEvents) if (mIsRunning && aEvents.Contains(kEventThreadNetdataChanged)) { - mRxRaTracker.HandleNetDataChange(); + Get().HandleNetDataChange(); mOnLinkPrefixManager.HandleNetDataChange(); ScheduleRoutingPolicyEvaluation(kAfterRandomDelay); } @@ -588,16 +587,16 @@ void RoutingManager::SendRouterAdvertisement(RouterAdvTxMode aRaTxMode) LogInfo("Preparing RA"); - if (mRxRaTracker.GetLocalRaHeaderToMirror().IsValid()) + if (Get().GetLocalRaHeaderToMirror().IsValid()) { - header = mRxRaTracker.GetLocalRaHeaderToMirror(); + header = Get().GetLocalRaHeaderToMirror(); } else { header.SetToDefault(); } - mRxRaTracker.SetHeaderFlagsOn(header); + Get().SetHeaderFlagsOn(header); header.SetSnacRouterFlag(); SuccessOrExit(error = raMsg.Append(header)); @@ -669,7 +668,7 @@ void RoutingManager::HandleRsSenderFinished(TimeMilli aStartTime) // the learned RA header if it is not refreshed during Router // Solicitation. - mRxRaTracker.RemoveOrDeprecateOldEntries(aStartTime); + Get().RemoveOrDeprecateOldEntries(aStartTime); ScheduleRoutingPolicyEvaluation(kImmediately); } @@ -691,7 +690,7 @@ void RoutingManager::HandleNeighborAdvertisement(const InfraIf::Icmp6Packet &aPa VerifyOrExit(aPacket.GetLength() >= sizeof(NeighborAdvertMessage)); naMsg = reinterpret_cast(aPacket.GetBytes()); - mRxRaTracker.ProcessNeighborAdvertMessage(*naMsg); + Get().ProcessNeighborAdvertMessage(*naMsg); exit: return; @@ -699,8 +698,8 @@ exit: void RoutingManager::HandleRouterAdvertisement(const InfraIf::Icmp6Packet &aPacket, const Ip6::Address &aSrcAddress) { - RouterAdvert::RxMessage raMsg(aPacket); - RouterAdvOrigin raOrigin = kAnotherRouter; + RouterAdvert::RxMessage raMsg(aPacket); + RxRaTracker::RouterAdvOrigin raOrigin = RxRaTracker::kAnotherRouter; OT_ASSERT(mIsRunning); @@ -710,7 +709,8 @@ void RoutingManager::HandleRouterAdvertisement(const InfraIf::Icmp6Packet &aPack if (mInfraIf.HasAddress(aSrcAddress)) { - raOrigin = mTxRaInfo.IsRaFromManager(raMsg) ? kThisBrRoutingManager : kThisBrOtherEntity; + raOrigin = + mTxRaInfo.IsRaFromManager(raMsg) ? RxRaTracker::kThisBrRoutingManager : RxRaTracker::kThisBrOtherEntity; } LogInfo("Received RA from %s on %s %s", aSrcAddress.ToString().AsCString(), mInfraIf.ToString().AsCString(), @@ -718,7 +718,7 @@ void RoutingManager::HandleRouterAdvertisement(const InfraIf::Icmp6Packet &aPack DumpDebg("[BR-CERT] direction=recv | type=RA |", aPacket.GetBytes(), aPacket.GetLength()); - mRxRaTracker.ProcessRouterAdvertMessage(raMsg, aSrcAddress, raOrigin); + Get().ProcessRouterAdvertMessage(raMsg, aSrcAddress, raOrigin); exit: return; @@ -726,7 +726,7 @@ exit: void RoutingManager::HandleRaPrefixTableChanged(void) { - // This is a callback from `mRxRaTracker` indicating that + // This is a callback from `RxRaTracker` indicating that // there has been a change in the table. VerifyOrExit(mIsRunning); @@ -750,7 +750,7 @@ void RoutingManager::HandleLocalOnLinkPrefixChanged(void) VerifyOrExit(mIsRunning); mRoutePublisher.Evaluate(); - mRxRaTracker.HandleLocalOnLinkPrefixChanged(); + Get().HandleLocalOnLinkPrefixChanged(); ScheduleRoutingPolicyEvaluation(kAfterRandomDelay); exit: @@ -815,8 +815,8 @@ void RoutingManager::CheckReachabilityToSendIcmpError(const Message &aMessage, c VerifyOrExit(matchesUlaOmrLowPrf); - VerifyOrExit(!mRxRaTracker.IsAddressOnLink(aIp6Header.GetDestination())); - VerifyOrExit(!mRxRaTracker.IsAddressReachableThroughExplicitRoute(aIp6Header.GetDestination())); + VerifyOrExit(!Get().IsAddressOnLink(aIp6Header.GetDestination())); + VerifyOrExit(!Get().IsAddressReachableThroughExplicitRoute(aIp6Header.GetDestination())); VerifyOrExit(!Get().IsNat64(aIp6Header.GetDestination())); LogInfo("Send ICMP unreachable for fwd msg with local ULA src and non-local dst"); @@ -837,7 +837,7 @@ exit: #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) -const char *RoutingManager::RouterAdvOriginToString(RouterAdvOrigin aRaOrigin) +const char *RoutingManager::RouterAdvOriginToString(RxRaTracker::RouterAdvOrigin aRaOrigin) { static const char *const kOriginStrings[] = { "", // (0) kAnotherRouter @@ -848,9 +848,9 @@ const char *RoutingManager::RouterAdvOriginToString(RouterAdvOrigin aRaOrigin) struct EnumCheck { InitEnumValidatorCounter(); - ValidateNextEnum(kAnotherRouter); - ValidateNextEnum(kThisBrRoutingManager); - ValidateNextEnum(kThisBrOtherEntity); + ValidateNextEnum(RxRaTracker::kAnotherRouter); + ValidateNextEnum(RxRaTracker::kThisBrRoutingManager); + ValidateNextEnum(RxRaTracker::kThisBrOtherEntity); }; return kOriginStrings[aRaOrigin]; @@ -867,7 +867,7 @@ RoutingManager::MultiAilDetector::MultiAilDetector(Instance &aInstance) : InstanceLocator(aInstance) , mDetected(false) , mNetDataPeerBrCount(0) - , mRxRaTrackerReachablePeerBrCount(0) + , mReachablePeerBrCount(0) , mTimer(aInstance) { } @@ -875,9 +875,9 @@ RoutingManager::MultiAilDetector::MultiAilDetector(Instance &aInstance) void RoutingManager::MultiAilDetector::Stop(void) { mTimer.Stop(); - mDetected = false; - mNetDataPeerBrCount = 0; - mRxRaTrackerReachablePeerBrCount = 0; + mDetected = false; + mNetDataPeerBrCount = 0; + mReachablePeerBrCount = 0; } void RoutingManager::MultiAilDetector::Evaluate(void) @@ -896,15 +896,15 @@ void RoutingManager::MultiAilDetector::Evaluate(void) mNetDataPeerBrCount = count; } - count = Get().mRxRaTracker.GetReachablePeerBrCount(); + count = Get().GetReachablePeerBrCount(); - if (count != mRxRaTrackerReachablePeerBrCount) + if (count != mReachablePeerBrCount) { - LogInfo("Reachable Peer BR count from RaTracker: %u -> %u", mRxRaTrackerReachablePeerBrCount, count); - mRxRaTrackerReachablePeerBrCount = count; + LogInfo("Reachable Peer BR count from RaTracker: %u -> %u", mReachablePeerBrCount, count); + mReachablePeerBrCount = count; } - detected = (mNetDataPeerBrCount > mRxRaTrackerReachablePeerBrCount); + detected = (mNetDataPeerBrCount > mReachablePeerBrCount); if (detected == mDetected) { @@ -926,7 +926,7 @@ void RoutingManager::MultiAilDetector::HandleTimer(void) LogNote("BRs on multi AIL detected - BRs are likely connected to different infra-links"); LogInfo("More peer BRs in netdata vs from rx RAs for past %lu seconds", ToUlong(Time::MsecToSec(kDetectTime))); LogInfo("NetData Peer BR count: %u, RaTracker reachable Peer BR count: %u", mNetDataPeerBrCount, - mRxRaTrackerReachablePeerBrCount); + mReachablePeerBrCount); mDetected = true; } else @@ -940,1408 +940,6 @@ void RoutingManager::MultiAilDetector::HandleTimer(void) #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE -//--------------------------------------------------------------------------------------------------------------------- -// RxRaTracker - -RoutingManager::RxRaTracker::RxRaTracker(Instance &aInstance) - : InstanceLocator(aInstance) - , mExpirationTimer(aInstance) - , mStaleTimer(aInstance) - , mRouterTimer(aInstance) - , mRdnssAddrTimer(aInstance) - , mSignalTask(aInstance) - , mRdnssAddrTask(aInstance) -{ - mLocalRaHeader.Clear(); -} - -void RoutingManager::RxRaTracker::Start(void) { HandleNetDataChange(); } - -void RoutingManager::RxRaTracker::Stop(void) -{ - mRouters.Free(); - mIfAddresses.Free(); - mLocalRaHeader.Clear(); - mDecisionFactors.Clear(); - - mExpirationTimer.Stop(); - mStaleTimer.Stop(); - mRouterTimer.Stop(); - mRdnssAddrTimer.Stop(); -} - -void RoutingManager::RxRaTracker::ProcessRouterAdvertMessage(const RouterAdvert::RxMessage &aRaMessage, - const Ip6::Address &aSrcAddress, - RouterAdvOrigin aRaOrigin) -{ - // Process a received RA message and update the prefix table. - - Router *router; - - switch (aRaOrigin) - { - case kThisBrOtherEntity: - case kThisBrRoutingManager: - UpdateIfAddresses(aSrcAddress); - break; - case kAnotherRouter: - break; - } - - VerifyOrExit(aRaOrigin != kThisBrRoutingManager); - - router = mRouters.FindMatching(aSrcAddress); - - if (router == nullptr) - { - Entry *newEntry = AllocateEntry(); - - if (newEntry == nullptr) - { - LogWarn("Received RA from too many routers, ignore RA from %s", aSrcAddress.ToString().AsCString()); - ExitNow(); - } - - router = newEntry; - router->Clear(); - router->mDiscoverTime = Get().GetUptimeInSeconds(); - router->mAddress = aSrcAddress; - - mRouters.Push(*newEntry); - } - - // RA message can indicate router provides default route in the RA - // message header and can also include an RIO for `::/0`. When - // processing an RA message, the preference and lifetime values - // in a `::/0` RIO override the preference and lifetime values in - // the RA header (per RFC 4191 section 3.1). - - ProcessRaHeader(aRaMessage.GetHeader(), *router, aRaOrigin); - - for (const Option &option : aRaMessage) - { - switch (option.GetType()) - { - case Option::kTypePrefixInfo: - ProcessPrefixInfoOption(static_cast(option), *router); - break; - - case Option::kTypeRouteInfo: - ProcessRouteInfoOption(static_cast(option), *router); - break; - - case Option::kTypeRecursiveDnsServer: - ProcessRecursiveDnsServerOption(static_cast(option), *router); - break; - - default: - break; - } - } - - router->mIsLocalDevice = (aRaOrigin == kThisBrOtherEntity); - - router->ResetReachabilityState(); - - Evaluate(); - -exit: - return; -} - -void RoutingManager::RxRaTracker::ProcessRaHeader(const RouterAdvert::Header &aRaHeader, - Router &aRouter, - RouterAdvOrigin aRaOrigin) -{ - Entry *entry; - Ip6::Prefix prefix; - - LogRaHeader(aRaHeader); - - aRouter.mManagedAddressConfigFlag = aRaHeader.IsManagedAddressConfigFlagSet(); - aRouter.mOtherConfigFlag = aRaHeader.IsOtherConfigFlagSet(); - aRouter.mSnacRouterFlag = aRaHeader.IsSnacRouterFlagSet(); - - if (aRaOrigin == kThisBrOtherEntity) - { - // Update `mLocalRaHeader`, which tracks the RA header of - // locally generated RA by another sw entity running on this - // device. - - RouterAdvert::Header oldHeader = mLocalRaHeader; - - if (aRaHeader.GetRouterLifetime() == 0) - { - mLocalRaHeader.Clear(); - } - else - { - mLocalRaHeader = aRaHeader; - mLocalRaHeaderUpdateTime = TimerMilli::GetNow(); - - // The checksum is set to zero which indicates to platform - // that it needs to do the calculation and update it. - - mLocalRaHeader.SetChecksum(0); - } - - if (mLocalRaHeader != oldHeader) - { - Get().ScheduleRoutingPolicyEvaluation(kAfterRandomDelay); - } - } - - prefix.Clear(); - entry = aRouter.mRoutePrefixes.FindMatching(prefix); - - if (entry == nullptr) - { - VerifyOrExit(aRaHeader.GetRouterLifetime() != 0); - - entry = AllocateEntry(); - - if (entry == nullptr) - { - LogWarn("Discovered too many prefixes, ignore default route from RA header"); - ExitNow(); - } - - entry->SetFrom(aRaHeader); - aRouter.mRoutePrefixes.Push(*entry); - } - else - { - entry->SetFrom(aRaHeader); - } - -exit: - return; -} - -void RoutingManager::RxRaTracker::ProcessPrefixInfoOption(const PrefixInfoOption &aPio, Router &aRouter) -{ - Ip6::Prefix prefix; - Entry *entry; - bool disregard; - - // We track all valid PIO prefixes with the on-link (`L`) flag. The - // `OnLinkPrefix` entries store other PIO flags and are used by - // `DecisionFactors` to determine if a ULA or non-ULA on-link - // prefix has been observed. This decision then guides - // `RoutePublisher` on which route to publish. For determining the - // favored on-link prefix, only eligible `OnLinkPrefix` entries are - // considered. These entries must meet specific conditions, such as - // having a valid 64-bit length and either the AutoAddrConfig - // (`A`) and/or Dhcp6PdPreferred (`P`) flag set. The full set of - // conditions is covered in `OnLinkPrefix::IsFavoredOver()`. - - VerifyOrExit(aPio.IsValid()); - aPio.GetPrefix(prefix); - VerifyOrExit(!prefix.IsLinkLocal() && !prefix.IsMulticast()); - - if (!aPio.IsOnLinkFlagSet()) - { - aRouter.mOnLinkPrefixes.RemoveMatching(prefix); - ExitNow(); - } - - // Disregard the PIO prefix if it matches our local on-link prefix, - // as this indicates it's likely from a peer Border Router connected - // to the same Thread mesh. - - disregard = (prefix == Get().mOnLinkPrefixManager.GetLocalPrefix()); - -#if !OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE - VerifyOrExit(!disregard); -#endif - - LogPrefixInfoOption(prefix, aPio.GetValidLifetime(), aPio.GetPreferredLifetime(), aPio.GetFlags()); - - entry = aRouter.mOnLinkPrefixes.FindMatching(prefix); - - if (entry == nullptr) - { - VerifyOrExit(aPio.GetValidLifetime() != 0); - - entry = AllocateEntry(); - - if (entry == nullptr) - { - LogWarn("Discovered too many prefixes, ignore on-link prefix %s", prefix.ToString().AsCString()); - ExitNow(); - } - - entry->SetFrom(aPio); - aRouter.mOnLinkPrefixes.Push(*entry); - } - else - { - OnLinkPrefix newPrefix; - - newPrefix.SetFrom(aPio); - entry->AdoptFlagsAndValidAndPreferredLifetimesFrom(newPrefix); - } - - entry->SetDisregardFlag(disregard); - -exit: - return; -} - -void RoutingManager::RxRaTracker::ProcessRouteInfoOption(const RouteInfoOption &aRio, Router &aRouter) -{ - Ip6::Prefix prefix; - Entry *entry; - bool disregard; - - VerifyOrExit(aRio.IsValid()); - aRio.GetPrefix(prefix); - - VerifyOrExit(!prefix.IsLinkLocal() && !prefix.IsMulticast()); - - // Disregard our own advertised OMR prefixes and those currently - // present in the Thread Network Data. This implies it is likely - // from a peer Thread BR connected to the same Thread mesh. - // - // There should be eventual parity between the `RioAdvertiser` - // prefixes and the OMR prefixes in Network Data, but temporary - // discrepancies can occur due to the tx timing of RAs and time - // required to update Network Data (registering with leader). So - // both checks are necessary. - - disregard = (Get().mOmrPrefixManager.GetLocalPrefix().GetPrefix() == prefix) || - Get().mRioAdvertiser.HasAdvertised(prefix) || - Get().ContainsOmrPrefix(prefix); - -#if !OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE - VerifyOrExit(!disregard); -#endif - - LogRouteInfoOption(prefix, aRio.GetRouteLifetime(), aRio.GetPreference()); - - entry = aRouter.mRoutePrefixes.FindMatching(prefix); - - if (entry == nullptr) - { - VerifyOrExit(aRio.GetRouteLifetime() != 0); - - entry = AllocateEntry(); - - if (entry == nullptr) - { - LogWarn("Discovered too many prefixes, ignore route prefix %s", prefix.ToString().AsCString()); - ExitNow(); - } - - entry->SetFrom(aRio); - aRouter.mRoutePrefixes.Push(*entry); - } - else - { - entry->SetFrom(aRio); - } - - entry->SetDisregardFlag(disregard); - -exit: - return; -} - -void RoutingManager::RxRaTracker::ProcessRecursiveDnsServerOption(const RecursiveDnsServerOption &aRdnss, - Router &aRouter) -{ - Entry *entry; - bool didChange = false; - uint32_t lifetime; - - VerifyOrExit(aRdnss.IsValid()); - - lifetime = aRdnss.GetLifetime(); - - for (uint16_t index = 0; index < aRdnss.GetNumAddresses(); index++) - { - const Ip6::Address &address = aRdnss.GetAddressAt(index); - - LogRecursiveDnsServerOption(address, lifetime); - - if (lifetime == 0) - { - didChange |= (aRouter.mRdnssAddresses.RemoveAndFreeAllMatching(address)); - continue; - } - - entry = aRouter.mRdnssAddresses.FindMatching(address); - - if (entry != nullptr) - { - entry->SetFrom(aRdnss, index); - } - else - { - entry = AllocateEntry(); - - if (entry == nullptr) - { - LogWarn("Discovered too many entries, ignore RDNSS address %s", address.ToString().AsCString()); - ExitNow(); - } - - entry->SetFrom(aRdnss, index); - aRouter.mRdnssAddresses.Push(*entry); - didChange = true; - } - } - -exit: - if (didChange) - { - mRdnssAddrTask.Post(); - } -} - -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 <> -RoutingManager::RxRaTracker::Entry *RoutingManager::RxRaTracker::AllocateEntry( - void) -{ - Entry *router = mRouterPool.Allocate(); - - VerifyOrExit(router != nullptr); - router->Init(GetInstance()); - -exit: - return router; -} - -template RoutingManager::RxRaTracker::Entry *RoutingManager::RxRaTracker::AllocateEntry(void) -{ - static_assert(TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue || - TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue, - "Type MUST be RoutePrefix, OnLinkPrefix, RdnssAddress, or IfAddress"); - - Entry *entry = nullptr; - SharedEntry *sharedEntry = mEntryPool.Allocate(); - - VerifyOrExit(sharedEntry != nullptr); - entry = &sharedEntry->GetEntry(); - entry->Init(GetInstance()); - -exit: - return entry; -} - -template <> void RoutingManager::RxRaTracker::Entry::Free(void) -{ - mOnLinkPrefixes.Free(); - mRoutePrefixes.Free(); - mRdnssAddresses.Free(); - Get().mRxRaTracker.mRouterPool.Free(*this); -} - -template void RoutingManager::RxRaTracker::Entry::Free(void) -{ - static_assert(TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue || - TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue, - "Type MUST be RoutePrefix, OnLinkPrefix, RdnssAddress, or IfAddress"); - - Get().mRxRaTracker.mEntryPool.Free(*reinterpret_cast(this)); -} - -#endif // !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE - -void RoutingManager::RxRaTracker::HandleLocalOnLinkPrefixChanged(void) -{ - const Ip6::Prefix &prefix = Get().mOnLinkPrefixManager.GetLocalPrefix(); - bool didChange = false; - - // When `TRACK_PEER_BR_INFO_ENABLE` is enabled, we mark - // to disregard any on-link prefix entries matching the new - // local on-link prefix. Otherwise, we can remove and free - // them. - - for (Router &router : mRouters) - { -#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE - OnLinkPrefix *entry = router.mOnLinkPrefixes.FindMatching(prefix); - - if ((entry != nullptr) && !entry->ShouldDisregard()) - { - entry->SetDisregardFlag(true); - didChange = true; - } -#else - didChange |= router.mOnLinkPrefixes.RemoveAndFreeAllMatching(prefix); -#endif - } - - VerifyOrExit(didChange); - - Evaluate(); - -exit: - return; -} - -void RoutingManager::RxRaTracker::HandleNetDataChange(void) -{ - NetworkData::Iterator iterator = NetworkData::kIteratorInit; - NetworkData::OnMeshPrefixConfig prefixConfig; - bool didChange = false; - - while (Get().GetNext(iterator, prefixConfig) == kErrorNone) - { - if (!IsValidOmrPrefix(prefixConfig)) - { - continue; - } - - for (Router &router : mRouters) - { -#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE - RoutePrefix *entry = router.mRoutePrefixes.FindMatching(prefixConfig.GetPrefix()); - - if ((entry != nullptr) && !entry->ShouldDisregard()) - { - entry->SetDisregardFlag(true); - didChange = true; - } -#else - didChange |= router.mRoutePrefixes.RemoveAndFreeAllMatching(prefixConfig.GetPrefix()); -#endif - } - } - - if (didChange) - { - Evaluate(); - } -} - -void RoutingManager::RxRaTracker::RemoveOrDeprecateOldEntries(TimeMilli aTimeThreshold) -{ - // Remove route prefix entries and deprecate on-link entries in - // the table that are old (not updated since `aTimeThreshold`). - - for (Router &router : mRouters) - { - for (OnLinkPrefix &entry : router.mOnLinkPrefixes) - { - if (entry.GetLastUpdateTime() <= aTimeThreshold) - { - entry.ClearPreferredLifetime(); - } - } - - for (RoutePrefix &entry : router.mRoutePrefixes) - { - if (entry.GetLastUpdateTime() <= aTimeThreshold) - { - entry.ClearValidLifetime(); - } - } - - for (RdnssAddress &entry : router.mRdnssAddresses) - { - if (entry.GetLastUpdateTime() <= aTimeThreshold) - { - entry.ClearLifetime(); - } - } - } - - if (mLocalRaHeader.IsValid() && (mLocalRaHeaderUpdateTime <= aTimeThreshold)) - { - mLocalRaHeader.Clear(); - } - - Evaluate(); -} - -void RoutingManager::RxRaTracker::Evaluate(void) -{ - DecisionFactors oldFactors = mDecisionFactors; - TimeMilli now = TimerMilli::GetNow(); - NextFireTime routerTimeoutTime(now); - NextFireTime entryExpireTime(now); - NextFireTime staleTime(now); - NextFireTime rdnsssAddrExpireTime(now); - RouterList removedRouters; - - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Remove expired entries associated with each router - - for (Router &router : mRouters) - { - ExpirationChecker expirationChecker(now); - - router.mOnLinkPrefixes.RemoveAndFreeAllMatching(expirationChecker); - router.mRoutePrefixes.RemoveAndFreeAllMatching(expirationChecker); - - if (router.mRdnssAddresses.RemoveAndFreeAllMatching(expirationChecker)) - { - mRdnssAddrTask.Post(); - } - } - - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Remove any router entry that no longer has any valid on-link - // or route prefixes, RDNSS addresses, or other relevant flags set. - - 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 - // ULA/non-ULA on-link/route prefix, M/O flags). - - mDecisionFactors.Clear(); - - for (Router &router : mRouters) - { - router.mAllEntriesDisregarded = true; - - mDecisionFactors.UpdateFlagsFrom(router); - - for (OnLinkPrefix &entry : router.mOnLinkPrefixes) - { - mDecisionFactors.UpdateFrom(entry); - entry.SetStaleTimeCalculated(false); - - router.mAllEntriesDisregarded &= entry.ShouldDisregard(); - } - - for (RoutePrefix &entry : router.mRoutePrefixes) - { - mDecisionFactors.UpdateFrom(entry); - entry.SetStaleTimeCalculated(false); - - router.mAllEntriesDisregarded &= entry.ShouldDisregard(); - } - } - -#if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE - mDecisionFactors.mReachablePeerBrCount = CountReachablePeerBrs(); -#endif - - if (oldFactors != mDecisionFactors) - { - mSignalTask.Post(); - } - - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Schedule timers - - // If multiple routers advertise the same on-link or route prefix, - // the stale time for the prefix is determined by the latest stale - // time among all corresponding entries. - // - // The "StaleTimeCalculated" flag is used to ensure stale time is - // calculated only once for each unique prefix. Initially, this - // flag is cleared on all entries. As we iterate over routers and - // their entries, `DetermineStaleTimeFor()` will consider all - // matching entries and mark "StaleTimeCalculated" flag on them. - - for (Router &router : mRouters) - { - if (router.ShouldCheckReachability()) - { - router.DetermineReachabilityTimeout(); - routerTimeoutTime.UpdateIfEarlier(router.mTimeoutTime); - } - - for (const OnLinkPrefix &entry : router.mOnLinkPrefixes) - { - entryExpireTime.UpdateIfEarlier(entry.GetExpireTime()); - - if (!entry.IsStaleTimeCalculated()) - { - DetermineStaleTimeFor(entry, staleTime); - } - } - - for (const RoutePrefix &entry : router.mRoutePrefixes) - { - entryExpireTime.UpdateIfEarlier(entry.GetExpireTime()); - - if (!entry.IsStaleTimeCalculated()) - { - DetermineStaleTimeFor(entry, staleTime); - } - } - - for (const RdnssAddress &entry : router.mRdnssAddresses) - { - rdnsssAddrExpireTime.UpdateIfEarlier(entry.GetExpireTime()); - } - } - - if (mLocalRaHeader.IsValid()) - { - uint16_t interval = kStaleTime; - - if (mLocalRaHeader.GetRouterLifetime() > 0) - { - interval = Min(interval, mLocalRaHeader.GetRouterLifetime()); - } - - staleTime.UpdateIfEarlier(CalculateClampedExpirationTime(mLocalRaHeaderUpdateTime, interval)); - } - - mRouterTimer.FireAt(routerTimeoutTime); - 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) -{ - TimeMilli prefixStaleTime = aStaleTime.GetNow(); - bool found = false; - - for (Router &router : mRouters) - { - for (OnLinkPrefix &entry : router.mOnLinkPrefixes) - { - if (!entry.Matches(aPrefix.GetPrefix())) - { - continue; - } - - entry.SetStaleTimeCalculated(true); - - if (entry.IsDeprecated()) - { - continue; - } - - prefixStaleTime = Max(prefixStaleTime, Max(aStaleTime.GetNow(), entry.GetStaleTime())); - found = true; - } - } - - if (found) - { - aStaleTime.UpdateIfEarlier(prefixStaleTime); - } -} - -void RoutingManager::RxRaTracker::DetermineStaleTimeFor(const RoutePrefix &aPrefix, NextFireTime &aStaleTime) -{ - TimeMilli prefixStaleTime = aStaleTime.GetNow(); - bool found = false; - - for (Router &router : mRouters) - { - for (RoutePrefix &entry : router.mRoutePrefixes) - { - if (!entry.Matches(aPrefix.GetPrefix())) - { - continue; - } - - entry.SetStaleTimeCalculated(true); - - prefixStaleTime = Max(prefixStaleTime, Max(aStaleTime.GetNow(), entry.GetStaleTime())); - found = true; - } - } - - if (found) - { - aStaleTime.UpdateIfEarlier(prefixStaleTime); - } -} - -void RoutingManager::RxRaTracker::HandleStaleTimer(void) -{ - VerifyOrExit(Get().IsRunning()); - - LogInfo("Stale timer expired"); - Get().mRsSender.Start(); - -exit: - return; -} - -void RoutingManager::RxRaTracker::HandleExpirationTimer(void) { Evaluate(); } - -void RoutingManager::RxRaTracker::HandleSignalTask(void) { Get().HandleRaPrefixTableChanged(); } - -void RoutingManager::RxRaTracker::HandleRdnssAddrTask(void) { mRdnssCallback.InvokeIfSet(); } - -void RoutingManager::RxRaTracker::ProcessNeighborAdvertMessage(const NeighborAdvertMessage &aNaMessage) -{ - Router *router; - - VerifyOrExit(aNaMessage.IsValid()); - - router = mRouters.FindMatching(aNaMessage.GetTargetAddress()); - VerifyOrExit(router != nullptr); - - LogInfo("Received NA from router %s", router->mAddress.ToString().AsCString()); - - router->ResetReachabilityState(); - - Evaluate(); - -exit: - return; -} - -void RoutingManager::RxRaTracker::HandleRouterTimer(void) -{ - TimeMilli now = TimerMilli::GetNow(); - - for (Router &router : mRouters) - { - if (!router.ShouldCheckReachability() || (router.mTimeoutTime > now)) - { - continue; - } - - router.mNsProbeCount++; - - if (router.IsReachable()) - { - router.mTimeoutTime = now + ((router.mNsProbeCount < Router::kMaxNsProbes) ? Router::kNsProbeRetryInterval - : Router::kNsProbeTimeout); - SendNeighborSolicitToRouter(router); - } - else - { - LogInfo("No response to all Neighbor Solicitations attempts from router %s - marking it unreachable", - router.mAddress.ToString().AsCString()); - - // Remove route prefix entries and deprecate on-link prefix entries - // of the unreachable router. - - for (OnLinkPrefix &entry : router.mOnLinkPrefixes) - { - if (!entry.IsDeprecated()) - { - entry.ClearPreferredLifetime(); - } - } - - for (RoutePrefix &entry : router.mRoutePrefixes) - { - entry.ClearValidLifetime(); - } - - for (RdnssAddress &entry : router.mRdnssAddresses) - { - entry.ClearLifetime(); - } - } - } - - Evaluate(); -} - -void RoutingManager::RxRaTracker::HandleRdnssAddrTimer(void) { Evaluate(); } - -void RoutingManager::RxRaTracker::SendNeighborSolicitToRouter(const Router &aRouter) -{ - InfraIf::Icmp6Packet packet; - NeighborSolicitHeader nsHdr; - TxMessage nsMsg; - InfraIf::LinkLayerAddress linkAddr; - - VerifyOrExit(!Get().mRsSender.IsInProgress()); - - nsHdr.SetTargetAddress(aRouter.mAddress); - SuccessOrExit(nsMsg.Append(nsHdr)); - - if (Get().GetLinkLayerAddress(linkAddr) == kErrorNone) - { - SuccessOrExit(nsMsg.AppendLinkLayerOption(linkAddr, Option::kSourceLinkLayerAddr)); - } - - nsMsg.GetAsPacket(packet); - - IgnoreError(Get().mInfraIf.Send(packet, aRouter.mAddress)); - - LogInfo("Sent Neighbor Solicitation to %s - attempt:%u/%u", aRouter.mAddress.ToString().AsCString(), - aRouter.mNsProbeCount, Router::kMaxNsProbes); - -exit: - return; -} - -void RoutingManager::RxRaTracker::SetHeaderFlagsOn(RouterAdvert::Header &aHeader) const -{ - if (mDecisionFactors.mHeaderManagedAddressConfigFlag) - { - aHeader.SetManagedAddressConfigFlag(); - } - - if (mDecisionFactors.mHeaderOtherConfigFlag) - { - aHeader.SetOtherConfigFlag(); - } -} - -bool RoutingManager::RxRaTracker::IsAddressOnLink(const Ip6::Address &aAddress) const -{ - bool isOnLink = Get().mOnLinkPrefixManager.AddressMatchesLocalPrefix(aAddress); - - VerifyOrExit(!isOnLink); - - for (const Router &router : mRouters) - { - for (const OnLinkPrefix &onLinkPrefix : router.mOnLinkPrefixes) - { - isOnLink = aAddress.MatchesPrefix(onLinkPrefix.GetPrefix()); - VerifyOrExit(!isOnLink); - } - } - -exit: - return isOnLink; -} - -bool RoutingManager::RxRaTracker::IsAddressReachableThroughExplicitRoute(const Ip6::Address &aAddress) const -{ - // Checks whether the `aAddress` matches any discovered route - // prefix excluding `::/0`. - - bool isReachable = false; - - for (const Router &router : mRouters) - { - for (const RoutePrefix &routePrefix : router.mRoutePrefixes) - { - if (routePrefix.GetPrefix().GetLength() == 0) - { - continue; - } - - isReachable = aAddress.MatchesPrefix(routePrefix.GetPrefix()); - VerifyOrExit(!isReachable); - } - } - -exit: - return isReachable; -} - -void RoutingManager::RxRaTracker::InitIterator(PrefixTableIterator &aIterator) const -{ - static_cast(aIterator).Init(mRouters.GetHead(), Get().GetUptimeInSeconds()); -} - -Error RoutingManager::RxRaTracker::GetNextEntry(PrefixTableIterator &aIterator, PrefixTableEntry &aEntry) const -{ - Error error = kErrorNone; - Iterator &iterator = static_cast(aIterator); - - ClearAllBytes(aEntry); - - SuccessOrExit(error = iterator.AdvanceToNextPrefixEntry()); - - iterator.GetRouter()->CopyInfoTo(aEntry.mRouter, iterator.GetInitTime(), iterator.GetInitUptime()); - - switch (iterator.GetPrefixType()) - { - case Iterator::kOnLinkPrefix: - iterator.GetEntry()->CopyInfoTo(aEntry, iterator.GetInitTime()); - break; - case Iterator::kRoutePrefix: - iterator.GetEntry()->CopyInfoTo(aEntry, iterator.GetInitTime()); - break; - } - -exit: - return error; -} - -Error RoutingManager::RxRaTracker::GetNextRouter(PrefixTableIterator &aIterator, RouterEntry &aEntry) const -{ - Error error = kErrorNone; - Iterator &iterator = static_cast(aIterator); - - ClearAllBytes(aEntry); - - SuccessOrExit(error = iterator.AdvanceToNextRouter(Iterator::kRouterIterator)); - iterator.GetRouter()->CopyInfoTo(aEntry, iterator.GetInitTime(), iterator.GetInitUptime()); - -exit: - return error; -} - -Error RoutingManager::RxRaTracker::GetNextRdnssAddr(PrefixTableIterator &aIterator, RdnssAddrEntry &aEntry) const -{ - Error error = kErrorNone; - Iterator &iterator = static_cast(aIterator); - - ClearAllBytes(aEntry); - - SuccessOrExit(error = iterator.AdvanceToNextRdnssAddrEntry()); - - iterator.GetRouter()->CopyInfoTo(aEntry.mRouter, iterator.GetInitTime(), iterator.GetInitUptime()); - iterator.GetEntry()->CopyInfoTo(aEntry, iterator.GetInitTime()); - -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 -{ - uint16_t count = 0; - - for (const Router &router : mRouters) - { - if (!router.mIsLocalDevice && router.IsPeerBr() && router.IsReachable()) - { - count++; - } - } - - return count; -} -#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 - -void RoutingManager::RxRaTracker::Iterator::Init(const Entry *aRoutersHead, uint32_t aUptime) -{ - SetInitUptime(aUptime); - SetInitTime(); - SetType(kUnspecified); - SetRouter(aRoutersHead); - SetEntry(nullptr); - SetPrefixType(kRoutePrefix); -} - -Error RoutingManager::RxRaTracker::Iterator::AdvanceToNextRouter(Type aType) -{ - Error error = kErrorNone; - - if (GetType() == kUnspecified) - { - // On the first call, when iterator type is `kUnspecified`, we - // set the type, and keep the `GetRouter()` as is so to start - // from the first router in the list. - - SetType(aType); - } - else - { - // On subsequent call, we ensure that the iterator type - // matches what we expect and advance to the next router on - // the list. - - VerifyOrExit(GetType() == aType, error = kErrorInvalidArgs); - VerifyOrExit(GetRouter() != nullptr, error = kErrorNone); - SetRouter(GetRouter()->GetNext()); - } - - VerifyOrExit(GetRouter() != nullptr, error = kErrorNotFound); - -exit: - return error; -} - -Error RoutingManager::RxRaTracker::Iterator::AdvanceToNextPrefixEntry(void) -{ - Error error = kErrorNone; - - VerifyOrExit(GetRouter() != nullptr, error = kErrorNotFound); - - if (HasEntry()) - { - switch (GetPrefixType()) - { - case kOnLinkPrefix: - SetEntry(GetEntry()->GetNext()); - break; - case kRoutePrefix: - SetEntry(GetEntry()->GetNext()); - break; - } - } - - while (!HasEntry()) - { - switch (GetPrefixType()) - { - case kOnLinkPrefix: - - // Transition from on-link prefixes to route prefixes of - // the current router. - - SetEntry(GetRouter()->mRoutePrefixes.GetHead()); - SetPrefixType(kRoutePrefix); - break; - - case kRoutePrefix: - - // Transition to the next router and start with its on-link - // prefixes. - // - // On the first call when iterator type is `kUnspecified`, - // `AdvanceToNextRouter()` sets the type and starts from - // the first router. - - SuccessOrExit(error = AdvanceToNextRouter(kPrefixIterator)); - SetEntry(GetRouter()->mOnLinkPrefixes.GetHead()); - SetPrefixType(kOnLinkPrefix); - break; - } - } - -exit: - return error; -} - -Error RoutingManager::RxRaTracker::Iterator::AdvanceToNextRdnssAddrEntry(void) -{ - Error error = kErrorNone; - - VerifyOrExit(GetRouter() != nullptr, error = kErrorNotFound); - - if (HasEntry()) - { - VerifyOrExit(GetType() == kRdnssAddrIterator, error = kErrorInvalidArgs); - SetEntry(GetEntry()->GetNext()); - } - - while (!HasEntry()) - { - SuccessOrExit(error = AdvanceToNextRouter(kRdnssAddrIterator)); - SetEntry(GetRouter()->mRdnssAddresses.GetHead()); - } - -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; -} - -//--------------------------------------------------------------------------------------------------------------------- -// RxRaTracker::Router - -bool RoutingManager::RxRaTracker::Router::ShouldCheckReachability(void) const -{ - // Perform reachability check (send NS probes) only if the router: - // - Is not already marked as unreachable (due to failed NS probes) - // - Is not the local device itself (to avoid potential issues with - // the platform receiving/processing NAs from itself). - - return IsReachable() && !mIsLocalDevice; -} - -void RoutingManager::RxRaTracker::Router::ResetReachabilityState(void) -{ - // Called when an RA or NA is received and processed. - - mNsProbeCount = 0; - mLastUpdateTime = TimerMilli::GetNow(); - mTimeoutTime = mLastUpdateTime + Random::NonCrypto::AddJitter(kReachableInterval, kJitter); -} - -void RoutingManager::RxRaTracker::Router::DetermineReachabilityTimeout(void) -{ - uint32_t interval; - - VerifyOrExit(ShouldCheckReachability()); - VerifyOrExit(mNsProbeCount == 0); - - // If all of the router's prefix entries are marked as - // disregarded (excluded from any decisions), it indicates that - // this router is likely a peer BR connected to the same Thread - // mesh. We use a longer reachability check interval for such - // peer BRs. - - interval = mAllEntriesDisregarded ? kPeerBrReachableInterval : kReachableInterval; - mTimeoutTime = mLastUpdateTime + Random::NonCrypto::AddJitter(interval, kJitter); - -exit: - return; -} - -bool RoutingManager::RxRaTracker::Router::Matches(const EmptyChecker &aChecker) -{ - OT_UNUSED_VARIABLE(aChecker); - - bool hasFlags = false; - - // Router can be removed if it does not advertise M or O flags and - // also does not have any advertised prefix entries (RIO/PIO) or - // RDNSS address entries. If the router already failed to respond - // to max NS probe attempts, we consider it as offline and - // therefore do not consider its flags anymore. - - if (IsReachable()) - { - hasFlags = (mManagedAddressConfigFlag || mOtherConfigFlag); - } - - return !hasFlags && mOnLinkPrefixes.IsEmpty() && mRoutePrefixes.IsEmpty() && mRdnssAddresses.IsEmpty(); -} - -bool RoutingManager::RxRaTracker::Router::IsPeerBr(void) const -{ - // Determines whether the router is a peer BR (connected to the - // same Thread mesh network). It must have at least one entry - // (on-link or route) and all entries should be marked to be - // disregarded. While this model is generally effective to detect - // peer BRs, it may not be 100% accurate in all scenarios. - - return mAllEntriesDisregarded && !(mOnLinkPrefixes.IsEmpty() && mRoutePrefixes.IsEmpty()); -} - -void RoutingManager::RxRaTracker::Router::CopyInfoTo(RouterEntry &aEntry, TimeMilli aNow, uint32_t aUptime) const -{ - aEntry.mAddress = mAddress; - aEntry.mMsecSinceLastUpdate = aNow - mLastUpdateTime; - aEntry.mAge = aUptime - mDiscoverTime; - aEntry.mManagedAddressConfigFlag = mManagedAddressConfigFlag; - aEntry.mOtherConfigFlag = mOtherConfigFlag; - aEntry.mSnacRouterFlag = mSnacRouterFlag; - aEntry.mIsLocalDevice = mIsLocalDevice; - aEntry.mIsReachable = IsReachable(); - 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 - -void RoutingManager::RxRaTracker::DecisionFactors::UpdateFlagsFrom(const Router &aRouter) -{ - // Determine the `M` and `O` flags to include in the RA message - // header to be emitted. - // - // If any discovered router on infrastructure which is not itself a - // stub router (e.g., another Thread BR) includes the `M` or `O` - // flag, we also include the same flag. - - VerifyOrExit(!aRouter.mSnacRouterFlag); - VerifyOrExit(aRouter.IsReachable()); - - if (aRouter.mManagedAddressConfigFlag) - { - mHeaderManagedAddressConfigFlag = true; - } - - if (aRouter.mOtherConfigFlag) - { - mHeaderOtherConfigFlag = true; - } - -exit: - return; -} - -void RoutingManager::RxRaTracker::DecisionFactors::UpdateFrom(const OnLinkPrefix &aOnLinkPrefix) -{ - VerifyOrExit(!aOnLinkPrefix.ShouldDisregard()); - - if (aOnLinkPrefix.GetPrefix().IsUniqueLocal()) - { - mHasUlaOnLink = true; - } - else - { - mHasNonUlaOnLink = true; - } - - if (aOnLinkPrefix.IsFavoredOver(mFavoredOnLinkPrefix)) - { - mFavoredOnLinkPrefix = aOnLinkPrefix.GetPrefix(); - } - -exit: - return; -} - -void RoutingManager::RxRaTracker::DecisionFactors::UpdateFrom(const RoutePrefix &aRoutePrefix) -{ - VerifyOrExit(!aRoutePrefix.ShouldDisregard()); - - if (!mHasNonUlaRoute) - { - mHasNonUlaRoute = !aRoutePrefix.GetPrefix().IsUniqueLocal(); - } - -exit: - return; -} - //--------------------------------------------------------------------------------------------------------------------- // OmrPrefixManager @@ -2931,7 +1529,7 @@ void RoutingManager::OnLinkPrefixManager::Evaluate(void) { VerifyOrExit(!Get().mRsSender.IsInProgress()); - SetAilPrefix(Get().mRxRaTracker.GetFavoredOnLinkPrefix()); + SetAilPrefix(Get().GetFavoredOnLinkPrefix()); if ((mAilPrefix.GetLength() == 0) || (mAilPrefix == mLocalPrefix)) { @@ -2983,12 +1581,12 @@ bool RoutingManager::OnLinkPrefixManager::IsInitalEvaluationDone(void) const void RoutingManager::OnLinkPrefixManager::HandleRaPrefixTableChanged(void) { - // This is a callback from `mRxRaTracker` indicating that + // This is a callback from `RxRaTracker` indicating that // there has been a change in the table. If the favored on-link // prefix has changed, we trigger a re-evaluation of the routing // policy. - if (Get().mRxRaTracker.GetFavoredOnLinkPrefix() != mAilPrefix) + if (Get().GetFavoredOnLinkPrefix() != mAilPrefix) { Get().ScheduleRoutingPolicyEvaluation(kAfterRandomDelay); } @@ -3604,15 +2202,15 @@ void RoutingManager::RoutePublisher::Evaluate(void) VerifyOrExit(Get().IsRunning()); if (Get().mOmrPrefixManager.GetFavoredPrefix().IsInfrastructureDerived() && - Get().mRxRaTracker.ContainsDefaultOrNonUlaRoutePrefix()) + Get().ContainsDefaultOrNonUlaRoutePrefix()) { newState = kPublishDefault; } - else if (Get().mRxRaTracker.ContainsNonUlaOnLinkPrefix()) + else if (Get().ContainsNonUlaOnLinkPrefix()) { newState = kPublishDefault; } - else if (Get().mRxRaTracker.ContainsUlaOnLinkPrefix() || + else if (Get().ContainsUlaOnLinkPrefix() || Get().mOnLinkPrefixManager.ShouldPublishUlaRoute()) { newState = kPublishUla; diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index 46ecea817..4c3c5d4c7 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -63,6 +63,7 @@ #include "border_router/br_tracker.hpp" #include "border_router/br_types.hpp" #include "border_router/infra_if.hpp" +#include "border_router/rx_ra_tracker.hpp" #include "common/array.hpp" #include "common/callback.hpp" #include "common/equatable.hpp" @@ -99,6 +100,7 @@ class RoutingManager : public InstanceLocator friend class ot::Notifier; friend class ot::Instance; friend class NetDataBrTracker; + friend class RxRaTracker; public: /** @@ -454,92 +456,6 @@ public: */ void HandleInfraIfStateChanged(void) { EvaluateState(); } - /** - * Initializes a `PrefixTableIterator`. - * - * An iterator can be initialized again to start from the beginning of the table. - * - * When iterating over entries in the table, to ensure the entry update times are consistent, they are given - * relative to the time the iterator was initialized. - * - * @param[out] aIterator The iterator to initialize. - */ - void InitPrefixTableIterator(PrefixTableIterator &aIterator) const { mRxRaTracker.InitIterator(aIterator); } - - /** - * Iterates over entries in the discovered prefix table. - * - * @param[in,out] aIterator An iterator. - * @param[out] aEntry A reference to the entry to populate. - * - * @retval kErrorNone Got the next entry, @p aEntry is updated and @p aIterator is advanced. - * @retval kErrorNotFound No more entries in the table. - */ - Error GetNextPrefixTableEntry(PrefixTableIterator &aIterator, PrefixTableEntry &aEntry) const - { - return mRxRaTracker.GetNextEntry(aIterator, aEntry); - } - - /** - * Iterates over discovered router entries on infrastructure link. - * - * @param[in,out] aIterator An iterator. - * @param[out] aEntry A reference to the entry to populate. - * - * @retval kErrorNone Got the next router info, @p aEntry is updated and @p aIterator is advanced. - * @retval kErrorNotFound No more routers. - */ - Error GetNextRouterEntry(PrefixTableIterator &aIterator, RouterEntry &aEntry) const - { - return mRxRaTracker.GetNextRouter(aIterator, aEntry); - } - - /** - * Iterates over the discovered Recursive DNS Server (RDNSS) address entries. - * - * @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 GetNextRdnssAddrEntry(PrefixTableIterator &aIterator, RdnssAddrEntry &aEntry) const - { - return mRxRaTracker.GetNextRdnssAddr(aIterator, aEntry); - } - - /** - * Sets the callback to be notified of changes to discovered Recursive DNS Server (RDNSS) address entries. - * - * A subsequent call to this method, replaces a previously set callback. - * - * @param[in] aCallback The callback function pointer. Can be `nullptr` if no callback is required. - * @param[in] aConext An arbitrary context information (used when invoking the callback). - * - */ - void SetRdnssAddrCallback(RdnssAddrCallback aCallback, void *aContext) - { - 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) const - { - return mRxRaTracker.GetNextIfAddr(aIterator, aEntry); - } - #if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE /** @@ -696,8 +612,6 @@ private: static constexpr uint32_t kDefaultOnLinkPrefixLifetime = 1800; static constexpr uint32_t kDefaultNat64PrefixLifetime = 300; - static constexpr uint32_t kStaleTime = 600; // 10 minutes. - // RA transmission constants (in milliseconds). Initially, three // RAs are sent with a short interval of 16 seconds (± 2 seconds // jitter). Subsequently, a longer, regular RA beacon interval of @@ -728,13 +642,6 @@ private: kAdvPrefixesFromNetData, }; - enum RouterAdvOrigin : uint8_t // Origin of a received Router Advert message. - { - kAnotherRouter, // From another router on infra-if. - kThisBrRoutingManager, // From this BR generated by `RoutingManager` itself. - kThisBrOtherEntity, // From this BR generated by another sw entity. - }; - enum ScheduleMode : uint8_t // Used in `ScheduleRoutingPolicyEvaluation()` { kImmediately, @@ -787,7 +694,7 @@ private: bool mDetected; uint16_t mNetDataPeerBrCount; - uint16_t mRxRaTrackerReachablePeerBrCount; + uint16_t mReachablePeerBrCount; DetectTimer mTimer; DetectCallback mCallback; }; @@ -796,327 +703,6 @@ private: //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void HandleRxRaTrackerSignalTask(void) { mRxRaTracker.HandleSignalTask(); } - void HandleRxRaTrackerRdnssAddrTask(void) { mRxRaTracker.HandleRdnssAddrTask(); } - void HandleRxRaTrackerExpirationTimer(void) { mRxRaTracker.HandleExpirationTimer(); } - void HandleRxRaTrackerStaleTimer(void) { mRxRaTracker.HandleStaleTimer(); } - void HandleRxRaTrackerRouterTimer(void) { mRxRaTracker.HandleRouterTimer(); } - void HandleRxRaTrackerRdnssAddrTimer(void) { mRxRaTracker.HandleRdnssAddrTimer(); } - - class RxRaTracker : public InstanceLocator - { - // Processes received RA and NA messages tracking a table of active - // routers and their advertised on-link and route prefixes. Also - // manages prefix lifetimes and router reachability (sending NS probes - // as needed). - // - // When there is any change in the table (an entry is added, removed, - // or modified), it signals the change to `RoutingManager` by calling - // `HandleRaPrefixTableChanged()` callback. A `Tasklet` is used for - // signalling which ensures that if there are multiple changes within - // the same flow of execution, the callback is invoked after all the - // changes are processed. - - friend class NetDataBrTracker; - - public: - explicit RxRaTracker(Instance &aInstance); - - void Start(void); - void Stop(void); - - void SetRdnssCallback(RdnssAddrCallback aCallback, void *aContext) { mRdnssCallback.Set(aCallback, aContext); } - - void ProcessRouterAdvertMessage(const RouterAdvert::RxMessage &aRaMessage, - const Ip6::Address &aSrcAddress, - RouterAdvOrigin aRaOrigin); - void ProcessNeighborAdvertMessage(const NeighborAdvertMessage &aNaMessage); - - // Decision factors - bool ContainsDefaultOrNonUlaRoutePrefix(void) const { return mDecisionFactors.mHasNonUlaRoute; } - bool ContainsNonUlaOnLinkPrefix(void) const { return mDecisionFactors.mHasNonUlaOnLink; } - bool ContainsUlaOnLinkPrefix(void) const { return mDecisionFactors.mHasUlaOnLink; } -#if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE - uint16_t GetReachablePeerBrCount(void) const { return mDecisionFactors.mReachablePeerBrCount; } -#endif - - const Ip6::Prefix &GetFavoredOnLinkPrefix(void) const { return mDecisionFactors.mFavoredOnLinkPrefix; } - void SetHeaderFlagsOn(RouterAdvert::Header &aHeader) const; - - const RouterAdvert::Header &GetLocalRaHeaderToMirror(void) const { return mLocalRaHeader; } - - bool IsAddressOnLink(const Ip6::Address &aAddress) const; - bool IsAddressReachableThroughExplicitRoute(const Ip6::Address &aAddress) const; - - // Iterating over discovered items - void InitIterator(PrefixTableIterator &aIterator) const; - 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); - void HandleLocalOnLinkPrefixChanged(void); - void HandleNetDataChange(void); - - // Tasklet or timer callbacks - void HandleSignalTask(void); - void HandleRdnssAddrTask(void); - void HandleExpirationTimer(void); - void HandleStaleTimer(void); - void HandleRouterTimer(void); - void HandleRdnssAddrTimer(void); - - private: - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - template - struct Entry : public Type, - public LinkedListEntry>, -#if OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE - public Heap::Allocatable> -#else - public InstanceLocatorInit -#endif - { -#if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE - void Init(Instance &aInstance) { InstanceLocatorInit::Init(aInstance); } - void Free(void); -#endif - - Entry *mNext; - }; - - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - struct Router : public Clearable - { - // Reachability timeout intervals before starting Neighbor - // Solicitation (NS) probes. Generally 60 seconds (± 2 - // seconds jitter) is used. For peer BRs a longer timeout - // of 200 seconds (3 min and 20 seconds) is used. This is - // selected to be longer than regular RA beacon interval - // of 3 minutes (± 15 seconds jitter). - - static constexpr uint32_t kReachableInterval = OPENTHREAD_CONFIG_BORDER_ROUTING_ROUTER_ACTIVE_CHECK_TIMEOUT; - static constexpr uint32_t kPeerBrReachableInterval = Time::kOneSecondInMsec * 200; - - static constexpr uint8_t kMaxNsProbes = 5; // Max number of NS probe attempts. - static constexpr uint32_t kNsProbeRetryInterval = 1000; // In msec. Time between NS probe attempts. - static constexpr uint32_t kNsProbeTimeout = 2000; // In msec. Max Wait time after last NS probe. - static constexpr uint32_t kJitter = 2000; // In msec. Jitter to randomize probe starts. - - static_assert(kMaxNsProbes < 255, "kMaxNsProbes MUST not be 255"); - - struct EmptyChecker - { - }; - -#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); - void DetermineReachabilityTimeout(void); - bool Matches(const Ip6::Address &aAddress) const { return aAddress == mAddress; } - bool Matches(const EmptyChecker &aChecker); - bool IsPeerBr(void) const; - void CopyInfoTo(RouterEntry &aEntry, TimeMilli aNow, uint32_t aUptime) const; - - using OnLinkPrefixList = OwningList>; - using RoutePrefixList = OwningList>; - using RdnssAddressList = OwningList>; - - // `mDiscoverTime` tracks the initial discovery time of - // this router. To accommodate longer durations, the - // `Uptime` is used, as `TimeMilli` (which uses `uint32_t` - // intervals) would be limited to tracking ~ 49 days. - // - // `mLastUpdateTime` tracks the most recent time an RA or - // NA was received from this router. It is bounded due to - // the frequency of reachability checks, so we can safely - // use `TimeMilli` for it. - - Ip6::Address mAddress; - OnLinkPrefixList mOnLinkPrefixes; - RoutePrefixList mRoutePrefixes; - RdnssAddressList mRdnssAddresses; - uint32_t mDiscoverTime; - TimeMilli mLastUpdateTime; - TimeMilli mTimeoutTime; - uint8_t mNsProbeCount; - bool mManagedAddressConfigFlag : 1; - bool mOtherConfigFlag : 1; - bool mSnacRouterFlag : 1; - bool mIsLocalDevice : 1; - bool mAllEntriesDisregarded : 1; -#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE - HistoryInfo mHistoryInfo; -#endif - }; - - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - struct Iterator : public PrefixTableIterator - { - enum Type : uint8_t - { - kUnspecified, - kRouterIterator, - kPrefixIterator, - kRdnssAddrIterator, - kIfAddrIterator, - kNetDataBrIterator, // Used by `NetDataPeerBrTracker` - }; - - enum PrefixType : uint8_t - { - kOnLinkPrefix, - kRoutePrefix, - }; - - void Init(const Entry *aRoutersHead, uint32_t aUptime); - Error AdvanceToNextRouter(Type aType); - Error AdvanceToNextPrefixEntry(void); - Error AdvanceToNextRdnssAddrEntry(void); - Error AdvanceToNextIfAddrEntry(const Entry *aListHead); - uint32_t GetInitUptime(void) const { return mData0; } - void SetInitUptime(uint32_t aUptime) { mData0 = aUptime; } - TimeMilli GetInitTime(void) const { return TimeMilli(mData1); } - void SetInitTime(void) { mData1 = TimerMilli::GetNow().GetValue(); } - const Entry *GetRouter(void) const { return static_cast *>(mPtr1); } - void SetRouter(const Entry *aRouter) { mPtr1 = aRouter; } - Type GetType(void) const { return static_cast(mData2); } - void SetType(Type aType) { mData2 = aType; } - const void *GetEntry(void) const { return mPtr2; } - void SetEntry(const void *aEntry) { mPtr2 = aEntry; } - bool HasEntry(void) const { return mPtr2 != nullptr; } - PrefixType GetPrefixType(void) const { return static_cast(mData3); } - void SetPrefixType(PrefixType aType) { mData3 = aType; } - - template const Entry *GetEntry(void) const - { - return static_cast *>(mPtr2); - } - }; - - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE - static constexpr uint16_t kMaxRouters = OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_DISCOVERED_ROUTERS; - static constexpr uint16_t kMaxEntries = OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_DISCOVERED_PREFIXES; - - union SharedEntry - { - SharedEntry(void) { mNext = nullptr; } - void SetNext(SharedEntry *aNext) { mNext = aNext; } - SharedEntry *GetNext(void) { return mNext; } - const SharedEntry *GetNext(void) const { return mNext; } - - template Entry &GetEntry(void); - - SharedEntry *mNext; - Entry mOnLinkEntry; - Entry mRouteEntry; - Entry mRdnssAddrEntry; - Entry mIfAddrEntry; - }; -#endif - - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - struct DecisionFactors : public Clearable, public Equatable - { - DecisionFactors(void) { Clear(); } - - bool HasFavoredOnLink(void) const { return (mFavoredOnLinkPrefix.GetLength() != 0); } - void UpdateFlagsFrom(const Router &aRouter); - void UpdateFrom(const OnLinkPrefix &aOnLinkPrefix); - void UpdateFrom(const RoutePrefix &aRoutePrefix); - - Ip6::Prefix mFavoredOnLinkPrefix; - bool mHasNonUlaRoute : 1; - bool mHasNonUlaOnLink : 1; - bool mHasUlaOnLink : 1; - bool mHeaderManagedAddressConfigFlag : 1; - bool mHeaderOtherConfigFlag : 1; -#if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE - uint16_t mReachablePeerBrCount; -#endif - }; - - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void ProcessRaHeader(const RouterAdvert::Header &aRaHeader, Router &aRouter, RouterAdvOrigin aRaOrigin); - 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); - void SendNeighborSolicitToRouter(const Router &aRouter); -#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 - template Entry *AllocateEntry(void); -#endif - - using SignalTask = TaskletIn; - using RdnssAddrTask = TaskletIn; - using ExpirationTimer = TimerMilliIn; - using StaleTimer = TimerMilliIn; - 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; - RdnssAddrTimer mRdnssAddrTimer; - SignalTask mSignalTask; - RdnssAddrTask mRdnssAddrTask; - RdnssCallback mRdnssCallback; - RouterAdvert::Header mLocalRaHeader; - TimeMilli mLocalRaHeaderUpdateTime; - -#if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE - Pool mEntryPool; - Pool, kMaxRouters> mRouterPool; -#endif - }; - - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class OmrPrefixManager : public InstanceLocator { public: @@ -1576,7 +1162,7 @@ private: static bool IsValidBrUlaPrefix(const Ip6::Prefix &aBrUlaPrefix); - static const char *RouterAdvOriginToString(RouterAdvOrigin aRaOrigin); + static const char *RouterAdvOriginToString(RxRaTracker::RouterAdvOrigin aRaOrigin); //------------------------------------------------------------------------------------------------------------------ // Variables @@ -1608,8 +1194,6 @@ private: MultiAilDetector mMultiAilDetector; #endif - RxRaTracker mRxRaTracker; - RoutePublisher mRoutePublisher; #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE @@ -1627,45 +1211,6 @@ private: RoutingPolicyTimer mRoutingPolicyTimer; }; -#if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE - -//---------------------------------------------------------------------------------------------------------------------- -// Template specializations and declarations - -template <> -inline RoutingManager::RxRaTracker::Entry &RoutingManager::RxRaTracker::SharedEntry::GetEntry(void) -{ - return mOnLinkEntry; -} - -template <> -inline RoutingManager::RxRaTracker::Entry &RoutingManager::RxRaTracker::SharedEntry::GetEntry(void) -{ - return mRouteEntry; -} - -template <> -inline RoutingManager::RxRaTracker::Entry &RoutingManager::RxRaTracker::SharedEntry::GetEntry(void) -{ - return mRdnssAddrEntry; -} - -template <> -inline RoutingManager::RxRaTracker::Entry &RoutingManager::RxRaTracker::SharedEntry::GetEntry(void) -{ - return mIfAddrEntry; -} - -// Declare template (full) specializations for `Router` type. - -template <> -RoutingManager::RxRaTracker::Entry *RoutingManager::RxRaTracker::AllocateEntry( - void); - -template <> void RoutingManager::RxRaTracker::Entry::Free(void); - -#endif // #if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE - } // namespace BorderRouter DefineMapEnum(otBorderRoutingState, BorderRouter::RoutingManager::State); diff --git a/src/core/border_router/rx_ra_tracker.cpp b/src/core/border_router/rx_ra_tracker.cpp new file mode 100644 index 000000000..93b84f168 --- /dev/null +++ b/src/core/border_router/rx_ra_tracker.cpp @@ -0,0 +1,1446 @@ +/* + * Copyright (c) 2025, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements the Received RA Tracker. + */ + +#include "rx_ra_tracker.hpp" + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + +#include "border_router/br_log.hpp" +#include "instance/instance.hpp" + +namespace ot { +namespace BorderRouter { + +RegisterLogModule("BorderRouting"); + +//--------------------------------------------------------------------------------------------------------------------- +// RxRaTracker + +RxRaTracker::RxRaTracker(Instance &aInstance) + : InstanceLocator(aInstance) + , mExpirationTimer(aInstance) + , mStaleTimer(aInstance) + , mRouterTimer(aInstance) + , mRdnssAddrTimer(aInstance) + , mSignalTask(aInstance) + , mRdnssAddrTask(aInstance) +{ + mLocalRaHeader.Clear(); +} + +void RxRaTracker::Start(void) { HandleNetDataChange(); } + +void RxRaTracker::Stop(void) +{ + mRouters.Free(); + mIfAddresses.Free(); + mLocalRaHeader.Clear(); + mDecisionFactors.Clear(); + + mExpirationTimer.Stop(); + mStaleTimer.Stop(); + mRouterTimer.Stop(); + mRdnssAddrTimer.Stop(); +} + +void RxRaTracker::ProcessRouterAdvertMessage(const RouterAdvert::RxMessage &aRaMessage, + const Ip6::Address &aSrcAddress, + RouterAdvOrigin aRaOrigin) +{ + // Process a received RA message and update the prefix table. + + Router *router; + + switch (aRaOrigin) + { + case kThisBrOtherEntity: + case kThisBrRoutingManager: + UpdateIfAddresses(aSrcAddress); + break; + case kAnotherRouter: + break; + } + + VerifyOrExit(aRaOrigin != kThisBrRoutingManager); + + router = mRouters.FindMatching(aSrcAddress); + + if (router == nullptr) + { + Entry *newEntry = AllocateEntry(); + + if (newEntry == nullptr) + { + LogWarn("Received RA from too many routers, ignore RA from %s", aSrcAddress.ToString().AsCString()); + ExitNow(); + } + + router = newEntry; + router->Clear(); + router->mDiscoverTime = Get().GetUptimeInSeconds(); + router->mAddress = aSrcAddress; + + mRouters.Push(*newEntry); + } + + // RA message can indicate router provides default route in the RA + // message header and can also include an RIO for `::/0`. When + // processing an RA message, the preference and lifetime values + // in a `::/0` RIO override the preference and lifetime values in + // the RA header (per RFC 4191 section 3.1). + + ProcessRaHeader(aRaMessage.GetHeader(), *router, aRaOrigin); + + for (const Option &option : aRaMessage) + { + switch (option.GetType()) + { + case Option::kTypePrefixInfo: + ProcessPrefixInfoOption(static_cast(option), *router); + break; + + case Option::kTypeRouteInfo: + ProcessRouteInfoOption(static_cast(option), *router); + break; + + case Option::kTypeRecursiveDnsServer: + ProcessRecursiveDnsServerOption(static_cast(option), *router); + break; + + default: + break; + } + } + + router->mIsLocalDevice = (aRaOrigin == kThisBrOtherEntity); + + router->ResetReachabilityState(); + + Evaluate(); + +exit: + return; +} + +void RxRaTracker::ProcessRaHeader(const RouterAdvert::Header &aRaHeader, Router &aRouter, RouterAdvOrigin aRaOrigin) +{ + Entry *entry; + Ip6::Prefix prefix; + + LogRaHeader(aRaHeader); + + aRouter.mManagedAddressConfigFlag = aRaHeader.IsManagedAddressConfigFlagSet(); + aRouter.mOtherConfigFlag = aRaHeader.IsOtherConfigFlagSet(); + aRouter.mSnacRouterFlag = aRaHeader.IsSnacRouterFlagSet(); + + if (aRaOrigin == kThisBrOtherEntity) + { + // Update `mLocalRaHeader`, which tracks the RA header of + // locally generated RA by another sw entity running on this + // device. + + RouterAdvert::Header oldHeader = mLocalRaHeader; + + if (aRaHeader.GetRouterLifetime() == 0) + { + mLocalRaHeader.Clear(); + } + else + { + mLocalRaHeader = aRaHeader; + mLocalRaHeaderUpdateTime = TimerMilli::GetNow(); + + // The checksum is set to zero which indicates to platform + // that it needs to do the calculation and update it. + + mLocalRaHeader.SetChecksum(0); + } + + if (mLocalRaHeader != oldHeader) + { + Get().ScheduleRoutingPolicyEvaluation(RoutingManager::kAfterRandomDelay); + } + } + + prefix.Clear(); + entry = aRouter.mRoutePrefixes.FindMatching(prefix); + + if (entry == nullptr) + { + VerifyOrExit(aRaHeader.GetRouterLifetime() != 0); + + entry = AllocateEntry(); + + if (entry == nullptr) + { + LogWarn("Discovered too many prefixes, ignore default route from RA header"); + ExitNow(); + } + + entry->SetFrom(aRaHeader); + aRouter.mRoutePrefixes.Push(*entry); + } + else + { + entry->SetFrom(aRaHeader); + } + +exit: + return; +} + +void RxRaTracker::ProcessPrefixInfoOption(const PrefixInfoOption &aPio, Router &aRouter) +{ + Ip6::Prefix prefix; + Entry *entry; + bool disregard; + + // We track all valid PIO prefixes with the on-link (`L`) flag. The + // `OnLinkPrefix` entries store other PIO flags and are used by + // `DecisionFactors` to determine if a ULA or non-ULA on-link + // prefix has been observed. This decision then guides + // `RoutePublisher` on which route to publish. For determining the + // favored on-link prefix, only eligible `OnLinkPrefix` entries are + // considered. These entries must meet specific conditions, such as + // having a valid 64-bit length and either the AutoAddrConfig + // (`A`) and/or Dhcp6PdPreferred (`P`) flag set. The full set of + // conditions is covered in `OnLinkPrefix::IsFavoredOver()`. + + VerifyOrExit(aPio.IsValid()); + aPio.GetPrefix(prefix); + VerifyOrExit(!prefix.IsLinkLocal() && !prefix.IsMulticast()); + + if (!aPio.IsOnLinkFlagSet()) + { + aRouter.mOnLinkPrefixes.RemoveMatching(prefix); + ExitNow(); + } + + // Disregard the PIO prefix if it matches our local on-link prefix, + // as this indicates it's likely from a peer Border Router connected + // to the same Thread mesh. + + disregard = (prefix == Get().mOnLinkPrefixManager.GetLocalPrefix()); + +#if !OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + VerifyOrExit(!disregard); +#endif + + LogPrefixInfoOption(prefix, aPio.GetValidLifetime(), aPio.GetPreferredLifetime(), aPio.GetFlags()); + + entry = aRouter.mOnLinkPrefixes.FindMatching(prefix); + + if (entry == nullptr) + { + VerifyOrExit(aPio.GetValidLifetime() != 0); + + entry = AllocateEntry(); + + if (entry == nullptr) + { + LogWarn("Discovered too many prefixes, ignore on-link prefix %s", prefix.ToString().AsCString()); + ExitNow(); + } + + entry->SetFrom(aPio); + aRouter.mOnLinkPrefixes.Push(*entry); + } + else + { + OnLinkPrefix newPrefix; + + newPrefix.SetFrom(aPio); + entry->AdoptFlagsAndValidAndPreferredLifetimesFrom(newPrefix); + } + + entry->SetDisregardFlag(disregard); + +exit: + return; +} + +void RxRaTracker::ProcessRouteInfoOption(const RouteInfoOption &aRio, Router &aRouter) +{ + Ip6::Prefix prefix; + Entry *entry; + bool disregard; + + VerifyOrExit(aRio.IsValid()); + aRio.GetPrefix(prefix); + + VerifyOrExit(!prefix.IsLinkLocal() && !prefix.IsMulticast()); + + // Disregard our own advertised OMR prefixes and those currently + // present in the Thread Network Data. This implies it is likely + // from a peer Thread BR connected to the same Thread mesh. + // + // There should be eventual parity between the `RioAdvertiser` + // prefixes and the OMR prefixes in Network Data, but temporary + // discrepancies can occur due to the tx timing of RAs and time + // required to update Network Data (registering with leader). So + // both checks are necessary. + + disregard = (Get().mOmrPrefixManager.GetLocalPrefix().GetPrefix() == prefix) || + Get().mRioAdvertiser.HasAdvertised(prefix) || + Get().ContainsOmrPrefix(prefix); + +#if !OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + VerifyOrExit(!disregard); +#endif + + LogRouteInfoOption(prefix, aRio.GetRouteLifetime(), aRio.GetPreference()); + + entry = aRouter.mRoutePrefixes.FindMatching(prefix); + + if (entry == nullptr) + { + VerifyOrExit(aRio.GetRouteLifetime() != 0); + + entry = AllocateEntry(); + + if (entry == nullptr) + { + LogWarn("Discovered too many prefixes, ignore route prefix %s", prefix.ToString().AsCString()); + ExitNow(); + } + + entry->SetFrom(aRio); + aRouter.mRoutePrefixes.Push(*entry); + } + else + { + entry->SetFrom(aRio); + } + + entry->SetDisregardFlag(disregard); + +exit: + return; +} + +void RxRaTracker::ProcessRecursiveDnsServerOption(const RecursiveDnsServerOption &aRdnss, Router &aRouter) +{ + Entry *entry; + bool didChange = false; + uint32_t lifetime; + + VerifyOrExit(aRdnss.IsValid()); + + lifetime = aRdnss.GetLifetime(); + + for (uint16_t index = 0; index < aRdnss.GetNumAddresses(); index++) + { + const Ip6::Address &address = aRdnss.GetAddressAt(index); + + LogRecursiveDnsServerOption(address, lifetime); + + if (lifetime == 0) + { + didChange |= (aRouter.mRdnssAddresses.RemoveAndFreeAllMatching(address)); + continue; + } + + entry = aRouter.mRdnssAddresses.FindMatching(address); + + if (entry != nullptr) + { + entry->SetFrom(aRdnss, index); + } + else + { + entry = AllocateEntry(); + + if (entry == nullptr) + { + LogWarn("Discovered too many entries, ignore RDNSS address %s", address.ToString().AsCString()); + ExitNow(); + } + + entry->SetFrom(aRdnss, index); + aRouter.mRdnssAddresses.Push(*entry); + didChange = true; + } + } + +exit: + if (didChange) + { + mRdnssAddrTask.Post(); + } +} + +void 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 <> RxRaTracker::Entry *RxRaTracker::AllocateEntry(void) +{ + Entry *router = mRouterPool.Allocate(); + + VerifyOrExit(router != nullptr); + router->Init(GetInstance()); + +exit: + return router; +} + +template RxRaTracker::Entry *RxRaTracker::AllocateEntry(void) +{ + static_assert(TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue || + TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue, + "Type MUST be RoutePrefix, OnLinkPrefix, RdnssAddress, or IfAddress"); + + Entry *entry = nullptr; + SharedEntry *sharedEntry = mEntryPool.Allocate(); + + VerifyOrExit(sharedEntry != nullptr); + entry = &sharedEntry->GetEntry(); + entry->Init(GetInstance()); + +exit: + return entry; +} + +template <> void RxRaTracker::Entry::Free(void) +{ + mOnLinkPrefixes.Free(); + mRoutePrefixes.Free(); + mRdnssAddresses.Free(); + Get().mRouterPool.Free(*this); +} + +template void RxRaTracker::Entry::Free(void) +{ + static_assert(TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue || + TypeTraits::IsSame::kValue || TypeTraits::IsSame::kValue, + "Type MUST be RoutePrefix, OnLinkPrefix, RdnssAddress, or IfAddress"); + + Get().mEntryPool.Free(*reinterpret_cast(this)); +} + +#endif // !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE + +void RxRaTracker::HandleLocalOnLinkPrefixChanged(void) +{ + const Ip6::Prefix &prefix = Get().mOnLinkPrefixManager.GetLocalPrefix(); + bool didChange = false; + + // When `TRACK_PEER_BR_INFO_ENABLE` is enabled, we mark + // to disregard any on-link prefix entries matching the new + // local on-link prefix. Otherwise, we can remove and free + // them. + + for (Router &router : mRouters) + { +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + OnLinkPrefix *entry = router.mOnLinkPrefixes.FindMatching(prefix); + + if ((entry != nullptr) && !entry->ShouldDisregard()) + { + entry->SetDisregardFlag(true); + didChange = true; + } +#else + didChange |= router.mOnLinkPrefixes.RemoveAndFreeAllMatching(prefix); +#endif + } + + VerifyOrExit(didChange); + + Evaluate(); + +exit: + return; +} + +void RxRaTracker::HandleNetDataChange(void) +{ + NetworkData::Iterator iterator = NetworkData::kIteratorInit; + NetworkData::OnMeshPrefixConfig prefixConfig; + bool didChange = false; + + while (Get().GetNext(iterator, prefixConfig) == kErrorNone) + { + if (!IsValidOmrPrefix(prefixConfig)) + { + continue; + } + + for (Router &router : mRouters) + { +#if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE + RoutePrefix *entry = router.mRoutePrefixes.FindMatching(prefixConfig.GetPrefix()); + + if ((entry != nullptr) && !entry->ShouldDisregard()) + { + entry->SetDisregardFlag(true); + didChange = true; + } +#else + didChange |= router.mRoutePrefixes.RemoveAndFreeAllMatching(prefixConfig.GetPrefix()); +#endif + } + } + + if (didChange) + { + Evaluate(); + } +} + +void RxRaTracker::RemoveOrDeprecateOldEntries(TimeMilli aTimeThreshold) +{ + // Remove route prefix entries and deprecate on-link entries in + // the table that are old (not updated since `aTimeThreshold`). + + for (Router &router : mRouters) + { + for (OnLinkPrefix &entry : router.mOnLinkPrefixes) + { + if (entry.GetLastUpdateTime() <= aTimeThreshold) + { + entry.ClearPreferredLifetime(); + } + } + + for (RoutePrefix &entry : router.mRoutePrefixes) + { + if (entry.GetLastUpdateTime() <= aTimeThreshold) + { + entry.ClearValidLifetime(); + } + } + + for (RdnssAddress &entry : router.mRdnssAddresses) + { + if (entry.GetLastUpdateTime() <= aTimeThreshold) + { + entry.ClearLifetime(); + } + } + } + + if (mLocalRaHeader.IsValid() && (mLocalRaHeaderUpdateTime <= aTimeThreshold)) + { + mLocalRaHeader.Clear(); + } + + Evaluate(); +} + +void RxRaTracker::Evaluate(void) +{ + DecisionFactors oldFactors = mDecisionFactors; + TimeMilli now = TimerMilli::GetNow(); + NextFireTime routerTimeoutTime(now); + NextFireTime entryExpireTime(now); + NextFireTime staleTime(now); + NextFireTime rdnsssAddrExpireTime(now); + RouterList removedRouters; + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Remove expired entries associated with each router + + for (Router &router : mRouters) + { + ExpirationChecker expirationChecker(now); + + router.mOnLinkPrefixes.RemoveAndFreeAllMatching(expirationChecker); + router.mRoutePrefixes.RemoveAndFreeAllMatching(expirationChecker); + + if (router.mRdnssAddresses.RemoveAndFreeAllMatching(expirationChecker)) + { + mRdnssAddrTask.Post(); + } + } + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Remove any router entry that no longer has any valid on-link + // or route prefixes, RDNSS addresses, or other relevant flags set. + + 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 + // ULA/non-ULA on-link/route prefix, M/O flags). + + mDecisionFactors.Clear(); + + for (Router &router : mRouters) + { + router.mAllEntriesDisregarded = true; + + mDecisionFactors.UpdateFlagsFrom(router); + + for (OnLinkPrefix &entry : router.mOnLinkPrefixes) + { + mDecisionFactors.UpdateFrom(entry); + entry.SetStaleTimeCalculated(false); + + router.mAllEntriesDisregarded &= entry.ShouldDisregard(); + } + + for (RoutePrefix &entry : router.mRoutePrefixes) + { + mDecisionFactors.UpdateFrom(entry); + entry.SetStaleTimeCalculated(false); + + router.mAllEntriesDisregarded &= entry.ShouldDisregard(); + } + } + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE + mDecisionFactors.mReachablePeerBrCount = CountReachablePeerBrs(); +#endif + + if (oldFactors != mDecisionFactors) + { + mSignalTask.Post(); + } + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Schedule timers + + // If multiple routers advertise the same on-link or route prefix, + // the stale time for the prefix is determined by the latest stale + // time among all corresponding entries. + // + // The "StaleTimeCalculated" flag is used to ensure stale time is + // calculated only once for each unique prefix. Initially, this + // flag is cleared on all entries. As we iterate over routers and + // their entries, `DetermineStaleTimeFor()` will consider all + // matching entries and mark "StaleTimeCalculated" flag on them. + + for (Router &router : mRouters) + { + if (router.ShouldCheckReachability()) + { + router.DetermineReachabilityTimeout(); + routerTimeoutTime.UpdateIfEarlier(router.mTimeoutTime); + } + + for (const OnLinkPrefix &entry : router.mOnLinkPrefixes) + { + entryExpireTime.UpdateIfEarlier(entry.GetExpireTime()); + + if (!entry.IsStaleTimeCalculated()) + { + DetermineStaleTimeFor(entry, staleTime); + } + } + + for (const RoutePrefix &entry : router.mRoutePrefixes) + { + entryExpireTime.UpdateIfEarlier(entry.GetExpireTime()); + + if (!entry.IsStaleTimeCalculated()) + { + DetermineStaleTimeFor(entry, staleTime); + } + } + + for (const RdnssAddress &entry : router.mRdnssAddresses) + { + rdnsssAddrExpireTime.UpdateIfEarlier(entry.GetExpireTime()); + } + } + + if (mLocalRaHeader.IsValid()) + { + uint16_t interval = kStaleTime; + + if (mLocalRaHeader.GetRouterLifetime() > 0) + { + interval = Min(interval, mLocalRaHeader.GetRouterLifetime()); + } + + staleTime.UpdateIfEarlier(CalculateClampedExpirationTime(mLocalRaHeaderUpdateTime, interval)); + } + + mRouterTimer.FireAt(routerTimeoutTime); + 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 RxRaTracker::DetermineStaleTimeFor(const OnLinkPrefix &aPrefix, NextFireTime &aStaleTime) +{ + TimeMilli prefixStaleTime = aStaleTime.GetNow(); + bool found = false; + + for (Router &router : mRouters) + { + for (OnLinkPrefix &entry : router.mOnLinkPrefixes) + { + if (!entry.Matches(aPrefix.GetPrefix())) + { + continue; + } + + entry.SetStaleTimeCalculated(true); + + if (entry.IsDeprecated()) + { + continue; + } + + prefixStaleTime = Max(prefixStaleTime, Max(aStaleTime.GetNow(), entry.GetStaleTime())); + found = true; + } + } + + if (found) + { + aStaleTime.UpdateIfEarlier(prefixStaleTime); + } +} + +void RxRaTracker::DetermineStaleTimeFor(const RoutePrefix &aPrefix, NextFireTime &aStaleTime) +{ + TimeMilli prefixStaleTime = aStaleTime.GetNow(); + bool found = false; + + for (Router &router : mRouters) + { + for (RoutePrefix &entry : router.mRoutePrefixes) + { + if (!entry.Matches(aPrefix.GetPrefix())) + { + continue; + } + + entry.SetStaleTimeCalculated(true); + + prefixStaleTime = Max(prefixStaleTime, Max(aStaleTime.GetNow(), entry.GetStaleTime())); + found = true; + } + } + + if (found) + { + aStaleTime.UpdateIfEarlier(prefixStaleTime); + } +} + +void RxRaTracker::HandleStaleTimer(void) +{ + VerifyOrExit(Get().IsRunning()); + + LogInfo("Stale timer expired"); + Get().mRsSender.Start(); + +exit: + return; +} + +void RxRaTracker::HandleExpirationTimer(void) { Evaluate(); } + +void RxRaTracker::HandleSignalTask(void) { Get().HandleRaPrefixTableChanged(); } + +void RxRaTracker::HandleRdnssAddrTask(void) { mRdnssCallback.InvokeIfSet(); } + +void RxRaTracker::ProcessNeighborAdvertMessage(const NeighborAdvertMessage &aNaMessage) +{ + Router *router; + + VerifyOrExit(aNaMessage.IsValid()); + + router = mRouters.FindMatching(aNaMessage.GetTargetAddress()); + VerifyOrExit(router != nullptr); + + LogInfo("Received NA from router %s", router->mAddress.ToString().AsCString()); + + router->ResetReachabilityState(); + + Evaluate(); + +exit: + return; +} + +void RxRaTracker::HandleRouterTimer(void) +{ + TimeMilli now = TimerMilli::GetNow(); + + for (Router &router : mRouters) + { + if (!router.ShouldCheckReachability() || (router.mTimeoutTime > now)) + { + continue; + } + + router.mNsProbeCount++; + + if (router.IsReachable()) + { + router.mTimeoutTime = now + ((router.mNsProbeCount < Router::kMaxNsProbes) ? Router::kNsProbeRetryInterval + : Router::kNsProbeTimeout); + SendNeighborSolicitToRouter(router); + } + else + { + LogInfo("No response to all Neighbor Solicitations attempts from router %s - marking it unreachable", + router.mAddress.ToString().AsCString()); + + // Remove route prefix entries and deprecate on-link prefix entries + // of the unreachable router. + + for (OnLinkPrefix &entry : router.mOnLinkPrefixes) + { + if (!entry.IsDeprecated()) + { + entry.ClearPreferredLifetime(); + } + } + + for (RoutePrefix &entry : router.mRoutePrefixes) + { + entry.ClearValidLifetime(); + } + + for (RdnssAddress &entry : router.mRdnssAddresses) + { + entry.ClearLifetime(); + } + } + } + + Evaluate(); +} + +void RxRaTracker::HandleRdnssAddrTimer(void) { Evaluate(); } + +void RxRaTracker::SendNeighborSolicitToRouter(const Router &aRouter) +{ + InfraIf::Icmp6Packet packet; + NeighborSolicitHeader nsHdr; + TxMessage nsMsg; + InfraIf::LinkLayerAddress linkAddr; + + VerifyOrExit(!Get().mRsSender.IsInProgress()); + + nsHdr.SetTargetAddress(aRouter.mAddress); + SuccessOrExit(nsMsg.Append(nsHdr)); + + if (Get().GetLinkLayerAddress(linkAddr) == kErrorNone) + { + SuccessOrExit(nsMsg.AppendLinkLayerOption(linkAddr, Option::kSourceLinkLayerAddr)); + } + + nsMsg.GetAsPacket(packet); + + IgnoreError(Get().mInfraIf.Send(packet, aRouter.mAddress)); + + LogInfo("Sent Neighbor Solicitation to %s - attempt:%u/%u", aRouter.mAddress.ToString().AsCString(), + aRouter.mNsProbeCount, Router::kMaxNsProbes); + +exit: + return; +} + +void RxRaTracker::SetHeaderFlagsOn(RouterAdvert::Header &aHeader) const +{ + if (mDecisionFactors.mHeaderManagedAddressConfigFlag) + { + aHeader.SetManagedAddressConfigFlag(); + } + + if (mDecisionFactors.mHeaderOtherConfigFlag) + { + aHeader.SetOtherConfigFlag(); + } +} + +bool RxRaTracker::IsAddressOnLink(const Ip6::Address &aAddress) const +{ + bool isOnLink = Get().mOnLinkPrefixManager.AddressMatchesLocalPrefix(aAddress); + + VerifyOrExit(!isOnLink); + + for (const Router &router : mRouters) + { + for (const OnLinkPrefix &onLinkPrefix : router.mOnLinkPrefixes) + { + isOnLink = aAddress.MatchesPrefix(onLinkPrefix.GetPrefix()); + VerifyOrExit(!isOnLink); + } + } + +exit: + return isOnLink; +} + +bool RxRaTracker::IsAddressReachableThroughExplicitRoute(const Ip6::Address &aAddress) const +{ + // Checks whether the `aAddress` matches any discovered route + // prefix excluding `::/0`. + + bool isReachable = false; + + for (const Router &router : mRouters) + { + for (const RoutePrefix &routePrefix : router.mRoutePrefixes) + { + if (routePrefix.GetPrefix().GetLength() == 0) + { + continue; + } + + isReachable = aAddress.MatchesPrefix(routePrefix.GetPrefix()); + VerifyOrExit(!isReachable); + } + } + +exit: + return isReachable; +} + +void RxRaTracker::InitIterator(PrefixTableIterator &aIterator) const +{ + static_cast(aIterator).Init(mRouters.GetHead(), Get().GetUptimeInSeconds()); +} + +Error RxRaTracker::GetNextPrefixTableEntry(PrefixTableIterator &aIterator, PrefixTableEntry &aEntry) const +{ + Error error = kErrorNone; + Iterator &iterator = static_cast(aIterator); + + ClearAllBytes(aEntry); + + SuccessOrExit(error = iterator.AdvanceToNextPrefixEntry()); + + iterator.GetRouter()->CopyInfoTo(aEntry.mRouter, iterator.GetInitTime(), iterator.GetInitUptime()); + + switch (iterator.GetPrefixType()) + { + case Iterator::kOnLinkPrefix: + iterator.GetEntry()->CopyInfoTo(aEntry, iterator.GetInitTime()); + break; + case Iterator::kRoutePrefix: + iterator.GetEntry()->CopyInfoTo(aEntry, iterator.GetInitTime()); + break; + } + +exit: + return error; +} + +Error RxRaTracker::GetNextRouterEntry(PrefixTableIterator &aIterator, RouterEntry &aEntry) const +{ + Error error = kErrorNone; + Iterator &iterator = static_cast(aIterator); + + ClearAllBytes(aEntry); + + SuccessOrExit(error = iterator.AdvanceToNextRouter(Iterator::kRouterIterator)); + iterator.GetRouter()->CopyInfoTo(aEntry, iterator.GetInitTime(), iterator.GetInitUptime()); + +exit: + return error; +} + +Error RxRaTracker::GetNextRdnssAddrEntry(PrefixTableIterator &aIterator, RdnssAddrEntry &aEntry) const +{ + Error error = kErrorNone; + Iterator &iterator = static_cast(aIterator); + + ClearAllBytes(aEntry); + + SuccessOrExit(error = iterator.AdvanceToNextRdnssAddrEntry()); + + iterator.GetRouter()->CopyInfoTo(aEntry.mRouter, iterator.GetInitTime(), iterator.GetInitUptime()); + iterator.GetEntry()->CopyInfoTo(aEntry, iterator.GetInitTime()); + +exit: + return error; +} + +Error RxRaTracker::GetNextIfAddrEntry(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 RxRaTracker::CountReachablePeerBrs(void) const +{ + uint16_t count = 0; + + for (const Router &router : mRouters) + { + if (!router.mIsLocalDevice && router.IsPeerBr() && router.IsReachable()) + { + count++; + } + } + + return count; +} +#endif + +#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE + +void 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 + +void RxRaTracker::Iterator::Init(const Entry *aRoutersHead, uint32_t aUptime) +{ + SetInitUptime(aUptime); + SetInitTime(); + SetType(kUnspecified); + SetRouter(aRoutersHead); + SetEntry(nullptr); + SetPrefixType(kRoutePrefix); +} + +Error RxRaTracker::Iterator::AdvanceToNextRouter(Type aType) +{ + Error error = kErrorNone; + + if (GetType() == kUnspecified) + { + // On the first call, when iterator type is `kUnspecified`, we + // set the type, and keep the `GetRouter()` as is so to start + // from the first router in the list. + + SetType(aType); + } + else + { + // On subsequent call, we ensure that the iterator type + // matches what we expect and advance to the next router on + // the list. + + VerifyOrExit(GetType() == aType, error = kErrorInvalidArgs); + VerifyOrExit(GetRouter() != nullptr, error = kErrorNone); + SetRouter(GetRouter()->GetNext()); + } + + VerifyOrExit(GetRouter() != nullptr, error = kErrorNotFound); + +exit: + return error; +} + +Error RxRaTracker::Iterator::AdvanceToNextPrefixEntry(void) +{ + Error error = kErrorNone; + + VerifyOrExit(GetRouter() != nullptr, error = kErrorNotFound); + + if (HasEntry()) + { + switch (GetPrefixType()) + { + case kOnLinkPrefix: + SetEntry(GetEntry()->GetNext()); + break; + case kRoutePrefix: + SetEntry(GetEntry()->GetNext()); + break; + } + } + + while (!HasEntry()) + { + switch (GetPrefixType()) + { + case kOnLinkPrefix: + + // Transition from on-link prefixes to route prefixes of + // the current router. + + SetEntry(GetRouter()->mRoutePrefixes.GetHead()); + SetPrefixType(kRoutePrefix); + break; + + case kRoutePrefix: + + // Transition to the next router and start with its on-link + // prefixes. + // + // On the first call when iterator type is `kUnspecified`, + // `AdvanceToNextRouter()` sets the type and starts from + // the first router. + + SuccessOrExit(error = AdvanceToNextRouter(kPrefixIterator)); + SetEntry(GetRouter()->mOnLinkPrefixes.GetHead()); + SetPrefixType(kOnLinkPrefix); + break; + } + } + +exit: + return error; +} + +Error RxRaTracker::Iterator::AdvanceToNextRdnssAddrEntry(void) +{ + Error error = kErrorNone; + + VerifyOrExit(GetRouter() != nullptr, error = kErrorNotFound); + + if (HasEntry()) + { + VerifyOrExit(GetType() == kRdnssAddrIterator, error = kErrorInvalidArgs); + SetEntry(GetEntry()->GetNext()); + } + + while (!HasEntry()) + { + SuccessOrExit(error = AdvanceToNextRouter(kRdnssAddrIterator)); + SetEntry(GetRouter()->mRdnssAddresses.GetHead()); + } + +exit: + return error; +} + +Error 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; +} + +//--------------------------------------------------------------------------------------------------------------------- +// RxRaTracker::Router + +bool RxRaTracker::Router::ShouldCheckReachability(void) const +{ + // Perform reachability check (send NS probes) only if the router: + // - Is not already marked as unreachable (due to failed NS probes) + // - Is not the local device itself (to avoid potential issues with + // the platform receiving/processing NAs from itself). + + return IsReachable() && !mIsLocalDevice; +} + +void RxRaTracker::Router::ResetReachabilityState(void) +{ + // Called when an RA or NA is received and processed. + + mNsProbeCount = 0; + mLastUpdateTime = TimerMilli::GetNow(); + mTimeoutTime = mLastUpdateTime + Random::NonCrypto::AddJitter(kReachableInterval, kJitter); +} + +void RxRaTracker::Router::DetermineReachabilityTimeout(void) +{ + uint32_t interval; + + VerifyOrExit(ShouldCheckReachability()); + VerifyOrExit(mNsProbeCount == 0); + + // If all of the router's prefix entries are marked as + // disregarded (excluded from any decisions), it indicates that + // this router is likely a peer BR connected to the same Thread + // mesh. We use a longer reachability check interval for such + // peer BRs. + + interval = mAllEntriesDisregarded ? kPeerBrReachableInterval : kReachableInterval; + mTimeoutTime = mLastUpdateTime + Random::NonCrypto::AddJitter(interval, kJitter); + +exit: + return; +} + +bool RxRaTracker::Router::Matches(const EmptyChecker &aChecker) +{ + OT_UNUSED_VARIABLE(aChecker); + + bool hasFlags = false; + + // Router can be removed if it does not advertise M or O flags and + // also does not have any advertised prefix entries (RIO/PIO) or + // RDNSS address entries. If the router already failed to respond + // to max NS probe attempts, we consider it as offline and + // therefore do not consider its flags anymore. + + if (IsReachable()) + { + hasFlags = (mManagedAddressConfigFlag || mOtherConfigFlag); + } + + return !hasFlags && mOnLinkPrefixes.IsEmpty() && mRoutePrefixes.IsEmpty() && mRdnssAddresses.IsEmpty(); +} + +bool RxRaTracker::Router::IsPeerBr(void) const +{ + // Determines whether the router is a peer BR (connected to the + // same Thread mesh network). It must have at least one entry + // (on-link or route) and all entries should be marked to be + // disregarded. While this model is generally effective to detect + // peer BRs, it may not be 100% accurate in all scenarios. + + return mAllEntriesDisregarded && !(mOnLinkPrefixes.IsEmpty() && mRoutePrefixes.IsEmpty()); +} + +void RxRaTracker::Router::CopyInfoTo(RouterEntry &aEntry, TimeMilli aNow, uint32_t aUptime) const +{ + aEntry.mAddress = mAddress; + aEntry.mMsecSinceLastUpdate = aNow - mLastUpdateTime; + aEntry.mAge = aUptime - mDiscoverTime; + aEntry.mManagedAddressConfigFlag = mManagedAddressConfigFlag; + aEntry.mOtherConfigFlag = mOtherConfigFlag; + aEntry.mSnacRouterFlag = mSnacRouterFlag; + aEntry.mIsLocalDevice = mIsLocalDevice; + aEntry.mIsReachable = IsReachable(); + aEntry.mIsPeerBr = IsPeerBr(); +} + +//--------------------------------------------------------------------------------------------------------------------- +// RxRaTracker::Router::HistoryInfo + +#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE + +void 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 + +void RxRaTracker::DecisionFactors::UpdateFlagsFrom(const Router &aRouter) +{ + // Determine the `M` and `O` flags to include in the RA message + // header to be emitted. + // + // If any discovered router on infrastructure which is not itself a + // stub router (e.g., another Thread BR) includes the `M` or `O` + // flag, we also include the same flag. + + VerifyOrExit(!aRouter.mSnacRouterFlag); + VerifyOrExit(aRouter.IsReachable()); + + if (aRouter.mManagedAddressConfigFlag) + { + mHeaderManagedAddressConfigFlag = true; + } + + if (aRouter.mOtherConfigFlag) + { + mHeaderOtherConfigFlag = true; + } + +exit: + return; +} + +void RxRaTracker::DecisionFactors::UpdateFrom(const OnLinkPrefix &aOnLinkPrefix) +{ + VerifyOrExit(!aOnLinkPrefix.ShouldDisregard()); + + if (aOnLinkPrefix.GetPrefix().IsUniqueLocal()) + { + mHasUlaOnLink = true; + } + else + { + mHasNonUlaOnLink = true; + } + + if (aOnLinkPrefix.IsFavoredOver(mFavoredOnLinkPrefix)) + { + mFavoredOnLinkPrefix = aOnLinkPrefix.GetPrefix(); + } + +exit: + return; +} + +void RxRaTracker::DecisionFactors::UpdateFrom(const RoutePrefix &aRoutePrefix) +{ + VerifyOrExit(!aRoutePrefix.ShouldDisregard()); + + if (!mHasNonUlaRoute) + { + mHasNonUlaRoute = !aRoutePrefix.GetPrefix().IsUniqueLocal(); + } + +exit: + return; +} + +} // namespace BorderRouter +} // namespace ot + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE diff --git a/src/core/border_router/rx_ra_tracker.hpp b/src/core/border_router/rx_ra_tracker.hpp new file mode 100644 index 000000000..2c4a23634 --- /dev/null +++ b/src/core/border_router/rx_ra_tracker.hpp @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2025, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for the Received RA Tracker. + */ + +#ifndef RX_RA_TRACKER_HPP_ +#define RX_RA_TRACKER_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + +#include + +#include "border_router/br_types.hpp" +#include "border_router/infra_if.hpp" +#include "common/callback.hpp" +#include "common/equatable.hpp" +#include "common/error.hpp" +#include "common/heap_allocatable.hpp" +#include "common/linked_list.hpp" +#include "common/locator.hpp" +#include "common/message.hpp" +#include "common/notifier.hpp" +#include "common/owning_list.hpp" +#include "common/pool.hpp" +#include "common/string.hpp" +#include "common/tasklet.hpp" +#include "common/timer.hpp" +#include "net/ip6.hpp" +#include "net/nd6.hpp" + +namespace ot { +namespace BorderRouter { + +class NetDataBrTracker; + +/** + * Implements processing and tracking of received Router Advertisement (RA) and Neighbor Advertisement (NA) messages. + * + * This class processes received RA and NA messages, tracking a table of active routers on the infrastructure link and + * their advertised on-link and route prefixes and other information. It also manages prefix lifetimes and router + * reachability (sending Neighbor Solicitation probes as needed). + */ +class RxRaTracker : public InstanceLocator +{ + friend class NetDataBrTracker; + +public: + /** + * Represents the origin of a received Router Advertisement message. + */ + enum RouterAdvOrigin : uint8_t + { + kAnotherRouter, ///< From another router on the infrastructure interface. + kThisBrRoutingManager, ///< From this Border Router, generated by `RoutingManager` itself. + kThisBrOtherEntity, ///< From this Border Router, generated by another software entity. + }; + + /** + * Initializes the `RxRaTracker` object. + * + * @param[in] aInstance The OpenThread instance. + */ + explicit RxRaTracker(Instance &aInstance); + + /** + * Starts the RA tracker. + */ + void Start(void); + + /** + * Stops the RA tracker. + */ + void Stop(void); + + /** + * Processes a received Router Advertisement (RA) message. + * + * @param[in] aRaMessage The received RA message. + * @param[in] aSrcAddress The source address of the RA message. + * @param[in] aRaOrigin The origin of the RA message. + */ + void ProcessRouterAdvertMessage(const RouterAdvert::RxMessage &aRaMessage, + const Ip6::Address &aSrcAddress, + RouterAdvOrigin aRaOrigin); + + /** + * Processes a received Neighbor Advertisement (NA) message. + * + * @param[in] aNaMessage The received NA message. + */ + void ProcessNeighborAdvertMessage(const NeighborAdvertMessage &aNaMessage); + + /** + * Initializes a `PrefixTableIterator`. + * + * An iterator can be initialized again to start from the beginning of the table. + * + * When iterating over entries in the table, to ensure the entry update times are consistent, they are given + * relative to the time the iterator was initialized. + * + * @param[out] aIterator The iterator to initialize. + */ + void InitIterator(PrefixTableIterator &aIterator) const; + + /** + * Iterates over entries in the discovered prefix table. + * + * @param[in,out] aIterator An iterator. + * @param[out] aEntry A reference to the entry to populate. + * + * @retval kErrorNone Got the next entry, @p aEntry is updated and @p aIterator is advanced. + * @retval kErrorNotFound No more entries in the table. + */ + Error GetNextPrefixTableEntry(PrefixTableIterator &aIterator, PrefixTableEntry &aEntry) const; + + /** + * Iterates over discovered router entries on infrastructure link. + * + * @param[in,out] aIterator An iterator. + * @param[out] aEntry A reference to the entry to populate. + * + * @retval kErrorNone Got the next router info, @p aEntry is updated and @p aIterator is advanced. + * @retval kErrorNotFound No more routers. + */ + Error GetNextRouterEntry(PrefixTableIterator &aIterator, RouterEntry &aEntry) const; + + /** + * Iterates over the discovered Recursive DNS Server (RDNSS) address entries. + * + * @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 GetNextRdnssAddrEntry(PrefixTableIterator &aIterator, RdnssAddrEntry &aEntry) const; + + /** + * Sets the callback to be notified of changes to discovered Recursive DNS Server (RDNSS) address entries. + * + * A subsequent call to this method, replaces a previously set callback. + * + * @param[in] aCallback The callback function pointer. Can be `nullptr` if no callback is required. + * @param[in] aConext An arbitrary context information (used when invoking the callback). + * + */ + void SetRdnssAddrCallback(RdnssAddrCallback aCallback, void *aContext) { mRdnssCallback.Set(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) const; + + /** + * Indicates whether a discovered route prefix is a default route or a non-ULA prefix. + * + * @retval TRUE A default route or a non-ULA route prefix is discovered. + * @retval FALSE No default route or non-ULA route prefix is discovered. + */ + bool ContainsDefaultOrNonUlaRoutePrefix(void) const { return mDecisionFactors.mHasNonUlaRoute; } + + /** + * Indicates whether a discovered on-link prefix is a non-ULA prefix. + * + * @retval TRUE A non-ULA on-link prefix is discovered. + * @retval FALSE No non-ULA on-link prefix is discovered. + */ + bool ContainsNonUlaOnLinkPrefix(void) const { return mDecisionFactors.mHasNonUlaOnLink; } + + /** + * Indicates whether a discovered on-link prefix is a ULA prefix. + * + * @retval TRUE A ULA on-link prefix is discovered. + * @retval FALSE No ULA on-link prefix is discovered. + */ + bool ContainsUlaOnLinkPrefix(void) const { return mDecisionFactors.mHasUlaOnLink; } + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE + /** + * Gets the number of reachable peer Border Routers. + * + * A peer BR is a BR on the same Thread mesh that is also connected to the same infrastructure link. + * + * @returns The number of reachable peer BRs. + */ + uint16_t GetReachablePeerBrCount(void) const { return mDecisionFactors.mReachablePeerBrCount; } +#endif + + /** + * Gets the favored on-link prefix among all discovered on-link prefixes. + * + * @returns The favored on-link prefix. + */ + const Ip6::Prefix &GetFavoredOnLinkPrefix(void) const { return mDecisionFactors.mFavoredOnLinkPrefix; } + + /** + * Sets the Managed Address Configuration (M) and Other Configuration (O) flags on a Router Advertisement header. + * + * The flags are set based on the currently discovered information from other routers on the infrastructure link. + * + * @param[out] aHeader The RA header to update. + */ + void SetHeaderFlagsOn(RouterAdvert::Header &aHeader) const; + + /** + * Gets the last received Router Advertisement header from a local software entity. + * + * This can be used to mirror the RA flags in the RA messages sent by the Border Router. + * + * @returns The last received RA header from a local software entity. + */ + const RouterAdvert::Header &GetLocalRaHeaderToMirror(void) const { return mLocalRaHeader; } + + /** + * Indicates whether an address is considered on-link on the infrastructure link. + * + * An address is considered on-link if it matches the local on-link prefix or any of the discovered on-link + * prefixes advertised by other routers. + * + * @param[in] aAddress The IPv6 address to check. + * + * @retval TRUE The address is on-link. + * @retval FALSE The address is not on-link. + */ + bool IsAddressOnLink(const Ip6::Address &aAddress) const; + + /** + * Indicates whether an address is reachable through an explicit route on the infrastructure link. + * + * This method checks if the given address matches any discovered route prefix, excluding the default route. + * + * @param[in] aAddress The IPv6 address to check. + * + * @retval TRUE The address is reachable through an explicit route. + * @retval FALSE The address is not reachable through an explicit route. + */ + bool IsAddressReachableThroughExplicitRoute(const Ip6::Address &aAddress) const; + + // Callbacks notifying of changes + void RemoveOrDeprecateOldEntries(TimeMilli aTimeThreshold); + void HandleLocalOnLinkPrefixChanged(void); + void HandleNetDataChange(void); + +private: + static constexpr uint32_t kStaleTime = 600; // 10 minutes. + + typedef Ip6::Nd::Option Option; + typedef Ip6::Nd::TxMessage TxMessage; + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + template + struct Entry : public Type, + public LinkedListEntry>, +#if OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE + public Heap::Allocatable> +#else + public InstanceLocatorInit +#endif + { +#if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE + void Init(Instance &aInstance) { InstanceLocatorInit::Init(aInstance); } + void Free(void); +#endif + + Entry *mNext; + }; + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + struct Router : public Clearable + { + // Reachability timeout intervals before starting Neighbor + // Solicitation (NS) probes. Generally 60 seconds (± 2 + // seconds jitter) is used. For peer BRs a longer timeout + // of 200 seconds (3 min and 20 seconds) is used. This is + // selected to be longer than regular RA beacon interval + // of 3 minutes (± 15 seconds jitter). + + static constexpr uint32_t kReachableInterval = OPENTHREAD_CONFIG_BORDER_ROUTING_ROUTER_ACTIVE_CHECK_TIMEOUT; + static constexpr uint32_t kPeerBrReachableInterval = Time::kOneSecondInMsec * 200; + + static constexpr uint8_t kMaxNsProbes = 5; // Max number of NS probe attempts. + static constexpr uint32_t kNsProbeRetryInterval = 1000; // In msec. Time between NS probe attempts. + static constexpr uint32_t kNsProbeTimeout = 2000; // In msec. Max Wait time after last NS probe. + static constexpr uint32_t kJitter = 2000; // In msec. Jitter to randomize probe starts. + + static_assert(kMaxNsProbes < 255, "kMaxNsProbes MUST not be 255"); + + struct EmptyChecker + { + }; + +#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); + void DetermineReachabilityTimeout(void); + bool Matches(const Ip6::Address &aAddress) const { return aAddress == mAddress; } + bool Matches(const EmptyChecker &aChecker); + bool IsPeerBr(void) const; + void CopyInfoTo(RouterEntry &aEntry, TimeMilli aNow, uint32_t aUptime) const; + + using OnLinkPrefixList = OwningList>; + using RoutePrefixList = OwningList>; + using RdnssAddressList = OwningList>; + + // `mDiscoverTime` tracks the initial discovery time of + // this router. To accommodate longer durations, the + // `Uptime` is used, as `TimeMilli` (which uses `uint32_t` + // intervals) would be limited to tracking ~ 49 days. + // + // `mLastUpdateTime` tracks the most recent time an RA or + // NA was received from this router. It is bounded due to + // the frequency of reachability checks, so we can safely + // use `TimeMilli` for it. + + Ip6::Address mAddress; + OnLinkPrefixList mOnLinkPrefixes; + RoutePrefixList mRoutePrefixes; + RdnssAddressList mRdnssAddresses; + uint32_t mDiscoverTime; + TimeMilli mLastUpdateTime; + TimeMilli mTimeoutTime; + uint8_t mNsProbeCount; + bool mManagedAddressConfigFlag : 1; + bool mOtherConfigFlag : 1; + bool mSnacRouterFlag : 1; + bool mIsLocalDevice : 1; + bool mAllEntriesDisregarded : 1; +#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE + HistoryInfo mHistoryInfo; +#endif + }; + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + struct Iterator : public PrefixTableIterator + { + enum Type : uint8_t + { + kUnspecified, + kRouterIterator, + kPrefixIterator, + kRdnssAddrIterator, + kIfAddrIterator, + kNetDataBrIterator, // Used by `NetDataPeerBrTracker` + }; + + enum PrefixType : uint8_t + { + kOnLinkPrefix, + kRoutePrefix, + }; + + void Init(const Entry *aRoutersHead, uint32_t aUptime); + Error AdvanceToNextRouter(Type aType); + Error AdvanceToNextPrefixEntry(void); + Error AdvanceToNextRdnssAddrEntry(void); + Error AdvanceToNextIfAddrEntry(const Entry *aListHead); + uint32_t GetInitUptime(void) const { return mData0; } + void SetInitUptime(uint32_t aUptime) { mData0 = aUptime; } + TimeMilli GetInitTime(void) const { return TimeMilli(mData1); } + void SetInitTime(void) { mData1 = TimerMilli::GetNow().GetValue(); } + const Entry *GetRouter(void) const { return static_cast *>(mPtr1); } + void SetRouter(const Entry *aRouter) { mPtr1 = aRouter; } + Type GetType(void) const { return static_cast(mData2); } + void SetType(Type aType) { mData2 = aType; } + const void *GetEntry(void) const { return mPtr2; } + void SetEntry(const void *aEntry) { mPtr2 = aEntry; } + bool HasEntry(void) const { return mPtr2 != nullptr; } + PrefixType GetPrefixType(void) const { return static_cast(mData3); } + void SetPrefixType(PrefixType aType) { mData3 = aType; } + + template const Entry *GetEntry(void) const + { + return static_cast *>(mPtr2); + } + }; + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +#if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE + static constexpr uint16_t kMaxRouters = OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_DISCOVERED_ROUTERS; + static constexpr uint16_t kMaxEntries = OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_DISCOVERED_PREFIXES; + + union SharedEntry + { + SharedEntry(void) { mNext = nullptr; } + void SetNext(SharedEntry *aNext) { mNext = aNext; } + SharedEntry *GetNext(void) { return mNext; } + const SharedEntry *GetNext(void) const { return mNext; } + + template Entry &GetEntry(void); + + SharedEntry *mNext; + Entry mOnLinkEntry; + Entry mRouteEntry; + Entry mRdnssAddrEntry; + Entry mIfAddrEntry; + }; +#endif + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + struct DecisionFactors : public Clearable, public Equatable + { + DecisionFactors(void) { Clear(); } + + bool HasFavoredOnLink(void) const { return (mFavoredOnLinkPrefix.GetLength() != 0); } + void UpdateFlagsFrom(const Router &aRouter); + void UpdateFrom(const OnLinkPrefix &aOnLinkPrefix); + void UpdateFrom(const RoutePrefix &aRoutePrefix); + + Ip6::Prefix mFavoredOnLinkPrefix; + bool mHasNonUlaRoute : 1; + bool mHasNonUlaOnLink : 1; + bool mHasUlaOnLink : 1; + bool mHeaderManagedAddressConfigFlag : 1; + bool mHeaderOtherConfigFlag : 1; +#if OPENTHREAD_CONFIG_BORDER_ROUTING_MULTI_AIL_DETECTION_ENABLE + uint16_t mReachablePeerBrCount; +#endif + }; + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + void ProcessRaHeader(const RouterAdvert::Header &aRaHeader, Router &aRouter, RouterAdvOrigin aRaOrigin); + 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); + void SendNeighborSolicitToRouter(const Router &aRouter); +#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 + // Tasklet or timer callbacks + void HandleSignalTask(void); + void HandleRdnssAddrTask(void); + void HandleExpirationTimer(void); + void HandleStaleTimer(void); + void HandleRouterTimer(void); + void HandleRdnssAddrTimer(void); + +#if OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE + template Entry *AllocateEntry(void) { return Entry::Allocate(); } +#else + template Entry *AllocateEntry(void); +#endif + + using SignalTask = TaskletIn; + using RdnssAddrTask = TaskletIn; + using ExpirationTimer = TimerMilliIn; + using StaleTimer = TimerMilliIn; + 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; + RdnssAddrTimer mRdnssAddrTimer; + SignalTask mSignalTask; + RdnssAddrTask mRdnssAddrTask; + RdnssCallback mRdnssCallback; + RouterAdvert::Header mLocalRaHeader; + TimeMilli mLocalRaHeaderUpdateTime; + +#if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE + Pool mEntryPool; + Pool, kMaxRouters> mRouterPool; +#endif +}; + +#if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE + +//---------------------------------------------------------------------------------------------------------------------- +// Template specializations and declarations + +template <> inline RxRaTracker::Entry &RxRaTracker::SharedEntry::GetEntry(void) { return mOnLinkEntry; } + +template <> inline RxRaTracker::Entry &RxRaTracker::SharedEntry::GetEntry(void) { return mRouteEntry; } + +template <> inline RxRaTracker::Entry &RxRaTracker::SharedEntry::GetEntry(void) +{ + return mRdnssAddrEntry; +} + +template <> inline RxRaTracker::Entry &RxRaTracker::SharedEntry::GetEntry(void) { return mIfAddrEntry; } + +// Declare template (full) specializations for `Router` type. + +template <> RxRaTracker::Entry *RxRaTracker::AllocateEntry(void); + +template <> void RxRaTracker::Entry::Free(void); + +#endif // #if !OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE + +} // namespace BorderRouter +} // namespace ot + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + +#endif // RX_RA_TRACKER_HPP_ diff --git a/src/core/instance/instance.cpp b/src/core/instance/instance.cpp index 1a5eb9908..e7324bac0 100644 --- a/src/core/instance/instance.cpp +++ b/src/core/instance/instance.cpp @@ -267,6 +267,7 @@ Instance::Instance(void) , mAnnounceSender(*this) #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + , mRxRaTracker(*this) , mRoutingManager(*this) #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE , mNetDataBrTracker(*this) diff --git a/src/core/instance/instance.hpp b/src/core/instance/instance.hpp index 97aa03f37..cd3a001f1 100644 --- a/src/core/instance/instance.hpp +++ b/src/core/instance/instance.hpp @@ -75,6 +75,7 @@ #include "backbone_router/bbr_manager.hpp" #include "border_router/dhcp6_pd_client.hpp" #include "border_router/routing_manager.hpp" +#include "border_router/rx_ra_tracker.hpp" #include "coap/coap_secure.hpp" #include "common/code_utils.hpp" #include "common/notifier.hpp" @@ -710,6 +711,7 @@ private: #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + BorderRouter::RxRaTracker mRxRaTracker; BorderRouter::RoutingManager mRoutingManager; #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE BorderRouter::NetDataBrTracker mNetDataBrTracker; @@ -1093,6 +1095,7 @@ template <> inline LinkMetrics::Subject &Instance::Get(void) { return mSubject; #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +template <> inline BorderRouter::RxRaTracker &Instance::Get(void) { return mRxRaTracker; } template <> inline BorderRouter::RoutingManager &Instance::Get(void) { return mRoutingManager; } template <> inline BorderRouter::InfraIf &Instance::Get(void) { return mRoutingManager.mInfraIf; } #if OPENTHREAD_CONFIG_BORDER_ROUTING_TRACK_PEER_BR_INFO_ENABLE diff --git a/src/core/utils/history_tracker.hpp b/src/core/utils/history_tracker.hpp index 17d0ef47e..9489eb9fa 100644 --- a/src/core/utils/history_tracker.hpp +++ b/src/core/utils/history_tracker.hpp @@ -42,6 +42,7 @@ #include #include "border_router/routing_manager.hpp" +#include "border_router/rx_ra_tracker.hpp" #include "common/as_core_type.hpp" #include "common/clearable.hpp" #include "common/locator.hpp" @@ -158,6 +159,7 @@ class Local : public InstanceLocator, private NonCopyable #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE friend class ot::BorderRouter::RoutingManager; + friend class ot::BorderRouter::RxRaTracker; #endif public: diff --git a/tests/unit/test_routing_manager.cpp b/tests/unit/test_routing_manager.cpp index 96b6bb7d0..760ff98d1 100644 --- a/tests/unit/test_routing_manager.cpp +++ b/tests/unit/test_routing_manager.cpp @@ -1080,9 +1080,9 @@ void VerifyPrefixTable(const OnLinkPrefix *aOnLinkPrefixes, Log("VerifyPrefixTable()"); - sInstance->Get().InitPrefixTableIterator(iter); + sInstance->Get().InitIterator(iter); - while (sInstance->Get().GetNextPrefixTableEntry(iter, entry) == kErrorNone) + while (sInstance->Get().GetNextPrefixTableEntry(iter, entry) == kErrorNone) { bool didFind = false; @@ -1168,9 +1168,9 @@ void VerifyRdnssAddressTable(const RdnssAddress *aRdnssAddresses, uint16_t aNumA Log("VerifyRdnssAddressTable()"); - sInstance->Get().InitPrefixTableIterator(iter); + sInstance->Get().InitIterator(iter); - while (sInstance->Get().GetNextRdnssAddrEntry(iter, entry) == kErrorNone) + while (sInstance->Get().GetNextRdnssAddrEntry(iter, entry) == kErrorNone) { bool didFind = false; @@ -1235,9 +1235,9 @@ void VerifyDiscoveredRouters(const InfraRouter *aRouters, uint16_t aNumRouters) Log("VerifyDiscoveredRouters()"); - sInstance->Get().InitPrefixTableIterator(iter); + sInstance->Get().InitIterator(iter); - while (sInstance->Get().GetNextRouterEntry(iter, entry) == kErrorNone) + while (sInstance->Get().GetNextRouterEntry(iter, entry) == kErrorNone) { bool didFind = false; @@ -4977,7 +4977,7 @@ void TestRdnss(void) // Set the RDNSS callback on Routing Manager rdnssCallbackCalled = false; - sInstance->Get().SetRdnssAddrCallback(HandleRdnssChanged, &rdnssCallbackCalled); + sInstance->Get().SetRdnssAddrCallback(HandleRdnssChanged, &rdnssCallbackCalled); VerifyOrQuit(!rdnssCallbackCalled);