diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index 007240069..7c4f3dac3 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -361,11 +361,6 @@ void RoutingManager::HandleNotifierEvents(Events aEvents) if (aEvents.Contains(kEventThreadExtPanIdChanged)) { mOnLinkPrefixManager.HandleExtPanIdChange(); - - if (mIsRunning) - { - ScheduleRoutingPolicyEvaluation(kAfterRandomDelay); - } } exit: @@ -480,6 +475,8 @@ void RoutingManager::EvaluatePublishingPrefix(const Ip6::Prefix &aPrefix) routeConfig.mPreference = NetworkData::kRoutePreferenceLow; routeConfig.mStable = true; + VerifyOrExit(mIsRunning); + // The order of checks is important. The Discovered Prefix Table is // first followed by Local On Link Prefix manager and finally NAT64 // prefix manager. @@ -507,6 +504,9 @@ void RoutingManager::EvaluatePublishingPrefix(const Ip6::Prefix &aPrefix) { UnpublishExternalRoute(aPrefix); } + +exit: + return; } void RoutingManager::UnpublishExternalRoute(const Ip6::Prefix &aPrefix) @@ -604,14 +604,16 @@ void RoutingManager::SendRouterAdvertisement(RouterAdvTxMode aRaTxMode) { // RA message max length is derived to accommodate: // - // - The RA header, - // - At most two PIOs (for current and old local on-link prefixes), + // - The RA header. + // - One PIO for current local on-link prefix. + // - At most `kMaxOldPrefixes` for old deprecating on-link prefixes. // - At most twice `kMaxOnMeshPrefixes` RIO for on-mesh prefixes. // Factor two is used for RIO to account for entries invalidating // previous prefixes while adding new ones. static constexpr uint16_t kMaxRaLength = - sizeof(Ip6::Nd::RouterAdvertMessage::Header) + sizeof(Ip6::Nd::PrefixInfoOption) * 2 + + sizeof(Ip6::Nd::RouterAdvertMessage::Header) + sizeof(Ip6::Nd::PrefixInfoOption) + + sizeof(Ip6::Nd::PrefixInfoOption) * OnLinkPrefixManager::kMaxOldPrefixes + 2 * kMaxOnMeshPrefixes * (sizeof(Ip6::Nd::RouteInfoOption) + sizeof(Ip6::Prefix)); uint8_t buffer[kMaxRaLength]; @@ -1915,60 +1917,73 @@ RoutingManager::OnLinkPrefixManager::OnLinkPrefixManager(Instance &aInstance) { mLocalPrefix.Clear(); mFavoredDiscoveredPrefix.Clear(); - mOldLocalPrefix.Clear(); + mOldLocalPrefixes.Clear(); } void RoutingManager::OnLinkPrefixManager::GenerateLocalPrefix(void) { - MeshCoP::ExtendedPanId extPanId = Get().GetExtPanId(); + const MeshCoP::ExtendedPanId &extPanId = Get().GetExtPanId(); + OldPrefix * entry; + + // Global ID: 40 most significant bits of Extended PAN ID + // Subnet ID: 16 least significant bits of Extended PAN ID mLocalPrefix.mPrefix.mFields.m8[0] = 0xfd; - // Global ID: 40 most significant bits of Extended PAN ID memcpy(mLocalPrefix.mPrefix.mFields.m8 + 1, extPanId.m8, 5); - // Subnet ID: 16 least significant bits of Extended PAN ID memcpy(mLocalPrefix.mPrefix.mFields.m8 + 6, extPanId.m8 + 6, 2); + mLocalPrefix.SetLength(kOnLinkPrefixLength); LogNote("Local on-link prefix: %s", mLocalPrefix.ToString().AsCString()); + + // Check if the new local prefix happens to be in `mOldLocalPrefixes` array. + // If so, we remove it from the array and set `mState` accordingly. + + entry = mOldLocalPrefixes.FindMatching(mLocalPrefix); + + if (entry != nullptr) + { + mState = kDeprecating; + mExpireTime = entry->mExpireTime; + mOldLocalPrefixes.Remove(*entry); + } + else + { + mState = kIdle; + } } void RoutingManager::OnLinkPrefixManager::Start(void) { - mState = kIdle; + Get().EvaluatePublishingPrefix(mLocalPrefix); + + for (const OldPrefix &oldPrefix : mOldLocalPrefixes) + { + Get().EvaluatePublishingPrefix(oldPrefix.mPrefix); + } } void RoutingManager::OnLinkPrefixManager::Stop(void) { - mTimer.Stop(); mFavoredDiscoveredPrefix.Clear(); - if (mOldLocalPrefix.GetLength() != 0) + for (const OldPrefix &oldPrefix : mOldLocalPrefixes) { - Get().UnpublishExternalRoute(mOldLocalPrefix); - mOldLocalPrefix.Clear(); + Get().UnpublishExternalRoute(oldPrefix.mPrefix); } - VerifyOrExit(mState != kIdle); - - Get().UnpublishExternalRoute(mLocalPrefix); - - if (mState == kPublishing) + switch (mState) { - // If we are waiting for prefix to be added in Network Data - // and not yet advertised it in any RA, there is no need to - // deprecate it and we can directly go to `kIdle` state. - mState = kIdle; - ExitNow(); + case kIdle: + break; + + case kPublishing: + case kAdvertising: + case kDeprecating: + Get().UnpublishExternalRoute(mLocalPrefix); + mState = kDeprecating; + break; } - - mState = kDeprecating; - - // Start deprecating the local on-link prefix to send a PIO - // with zero preferred lifetime in the next call to - // `SendRouterAdvertisement()`. - -exit: - return; } void RoutingManager::OnLinkPrefixManager::Evaluate(void) @@ -2069,6 +2084,7 @@ void RoutingManager::OnLinkPrefixManager::PublishAndAdvertise(void) } mState = kPublishing; + ResetExpireTime(TimerMilli::GetNow()); LogInfo("Publishing local on-link prefix %s in netdata", mLocalPrefix.ToString().AsCString()); Get().EvaluatePublishingPrefix(mLocalPrefix); @@ -2099,24 +2115,15 @@ void RoutingManager::OnLinkPrefixManager::Deprecate(void) switch (mState) { case kPublishing: - mState = kIdle; - Get().EvaluatePublishingPrefix(mLocalPrefix); - mState = kIdle; - break; - case kAdvertising: mState = kDeprecating; - mTimer.FireAtIfEarlier(mExpireTime); LogInfo("Deprecate local on-link prefix %s", mLocalPrefix.ToString().AsCString()); break; case kIdle: case kDeprecating: - ExitNow(); + break; } - -exit: - return; } bool RoutingManager::OnLinkPrefixManager::ShouldPublish(NetworkData::ExternalRouteConfig &aRouteConfig) const @@ -2137,7 +2144,7 @@ bool RoutingManager::OnLinkPrefixManager::ShouldPublish(NetworkData::ExternalRou break; } } - else if ((mOldLocalPrefix.GetLength() != 0) && (aRouteConfig.GetPrefix() == mOldLocalPrefix)) + else if (mOldLocalPrefixes.ContainsMatching(aRouteConfig.GetPrefix())) { shouldPublish = true; aRouteConfig.mPreference = NetworkData::kRoutePreferenceMedium; @@ -2146,11 +2153,15 @@ bool RoutingManager::OnLinkPrefixManager::ShouldPublish(NetworkData::ExternalRou return shouldPublish; } +void RoutingManager::OnLinkPrefixManager::ResetExpireTime(TimeMilli aNow) +{ + mExpireTime = aNow + TimeMilli::SecToMsec(kDefaultOnLinkPrefixLifetime); + mTimer.FireAtIfEarlier(mExpireTime); +} + void RoutingManager::OnLinkPrefixManager::EnterAdvertisingState(void) { - mState = kAdvertising; - mExpireTime = TimerMilli::GetNow() + TimeMilli::SecToMsec(kDefaultOnLinkPrefixLifetime); - + mState = kAdvertising; LogInfo("Start advertising local on-link prefix %s", mLocalPrefix.ToString().AsCString()); } @@ -2162,7 +2173,7 @@ bool RoutingManager::OnLinkPrefixManager::IsPublishingOrAdvertising(void) const void RoutingManager::OnLinkPrefixManager::AppendAsPiosTo(Ip6::Nd::RouterAdvertMessage &aRaMessage) { AppendCurPrefix(aRaMessage); - AppendOldPrefix(aRaMessage); + AppendOldPrefixes(aRaMessage); } void RoutingManager::OnLinkPrefixManager::AppendCurPrefix(Ip6::Nd::RouterAdvertMessage &aRaMessage) @@ -2181,7 +2192,7 @@ void RoutingManager::OnLinkPrefixManager::AppendCurPrefix(Ip6::Nd::RouterAdvertM switch (mState) { case kAdvertising: - mExpireTime = now + TimeMilli::SecToMsec(kDefaultOnLinkPrefixLifetime); + ResetExpireTime(now); break; case kDeprecating: @@ -2204,21 +2215,24 @@ exit: return; } -void RoutingManager::OnLinkPrefixManager::AppendOldPrefix(Ip6::Nd::RouterAdvertMessage &aRaMessage) +void RoutingManager::OnLinkPrefixManager::AppendOldPrefixes(Ip6::Nd::RouterAdvertMessage &aRaMessage) { TimeMilli now = TimerMilli::GetNow(); uint32_t validLifetime; - VerifyOrExit((mOldLocalPrefix.GetLength() != 0) && (mOldExpireTime > now)); + for (const OldPrefix &oldPrefix : mOldLocalPrefixes) + { + if (oldPrefix.mExpireTime < now) + { + continue; + } - validLifetime = TimeMilli::MsecToSec(mOldExpireTime - now); - SuccessOrAssert(aRaMessage.AppendPrefixInfoOption(mOldLocalPrefix, validLifetime, 0)); + validLifetime = TimeMilli::MsecToSec(oldPrefix.mExpireTime - now); + SuccessOrAssert(aRaMessage.AppendPrefixInfoOption(oldPrefix.mPrefix, validLifetime, 0)); - LogInfo("RouterAdvert: Added PIO for %s (valid=%u, preferred=0)", mOldLocalPrefix.ToString().AsCString(), - validLifetime); - -exit: - return; + LogInfo("RouterAdvert: Added PIO for %s (valid=%u, preferred=0)", oldPrefix.mPrefix.ToString().AsCString(), + validLifetime); + } } void RoutingManager::OnLinkPrefixManager::HandleNetDataChange(void) @@ -2237,72 +2251,133 @@ exit: void RoutingManager::OnLinkPrefixManager::HandleExtPanIdChange(void) { - // If the prefix is advertised or being deprecated we remember it - // as `mOldLocalPrefix` and deprecate it. It will be included in - // emitted RAs as PIO with zero preferred lifetime. It will still - // be present in Network Data until its expire time so to allow - // Thread nodes to continue to communicate with `InfraIf` devices - // using addresses based on this prefix. + // If the current local prefix is being advertised or deprecated, + // we save it in `mOldLocalPrefixes` and keep deprecating it . It will + // be included in emitted RAs as PIO with zero preferred lifetime. + // It will still be present in Network Data until its expire time + // so to allow Thread nodes to continue to communicate with `InfraIf` + // device using addresses based on this prefix. - switch (mState) + uint16_t oldState = mState; + Ip6::Prefix oldPrefix = mLocalPrefix; + + GenerateLocalPrefix(); + + switch (oldState) { case kIdle: break; case kPublishing: - Get().UnpublishExternalRoute(mLocalPrefix); + Get().EvaluatePublishingPrefix(oldPrefix); break; case kAdvertising: case kDeprecating: - if (mOldLocalPrefix.GetLength() != 0) - { - Ip6::Prefix prevPrefix = mOldLocalPrefix; - - mOldLocalPrefix.Clear(); - Get().EvaluatePublishingPrefix(prevPrefix); - } - - mOldLocalPrefix = mLocalPrefix; - mOldExpireTime = mExpireTime; - mTimer.FireAtIfEarlier(mOldExpireTime); + DeprecateOldPrefix(oldPrefix, mExpireTime); break; } - mState = kIdle; - GenerateLocalPrefix(); + if (Get().mIsRunning) + { + Get().ScheduleRoutingPolicyEvaluation(kAfterRandomDelay); + } +} + +void RoutingManager::OnLinkPrefixManager::DeprecateOldPrefix(const Ip6::Prefix &aPrefix, TimeMilli aExpireTime) +{ + OldPrefix * entry = nullptr; + Ip6::Prefix removedPrefix; + + removedPrefix.Clear(); + + VerifyOrExit(!mOldLocalPrefixes.ContainsMatching(aPrefix)); + + if (!mOldLocalPrefixes.IsFull()) + { + entry = mOldLocalPrefixes.PushBack(); + } + else + { + // If there is no more room in `mOldLocalPrefixes` array + // we evict the entry with the earliest expiration time. + + entry = &mOldLocalPrefixes[0]; + + for (OldPrefix &oldPrefix : mOldLocalPrefixes) + { + if ((oldPrefix.mExpireTime < entry->mExpireTime)) + { + entry = &oldPrefix; + } + } + + removedPrefix = entry->mPrefix; + } + + entry->mPrefix = aPrefix; + entry->mExpireTime = aExpireTime; + mTimer.FireAtIfEarlier(aExpireTime); + + Get().EvaluatePublishingPrefix(aPrefix); + + if (removedPrefix.GetLength() != 0) + { + Get().EvaluatePublishingPrefix(removedPrefix); + } + +exit: + return; } void RoutingManager::OnLinkPrefixManager::HandleTimer(void) { - TimeMilli now = TimerMilli::GetNow(); + TimeMilli now = TimerMilli::GetNow(); + TimeMilli nextExpireTime = now.GetDistantFuture(); + Array expiredPrefixes; - if ((mState == kDeprecating) && (now >= mExpireTime)) + switch (mState) { - LogInfo("Local on-link prefix %s expired", mLocalPrefix.ToString().AsCString()); - mState = kIdle; - Get().EvaluatePublishingPrefix(mLocalPrefix); + case kIdle: + break; + case kPublishing: + case kAdvertising: + case kDeprecating: + if (now >= mExpireTime) + { + LogInfo("Local on-link prefix %s expired", mLocalPrefix.ToString().AsCString()); + mState = kIdle; + Get().EvaluatePublishingPrefix(mLocalPrefix); + } + else + { + nextExpireTime = mExpireTime; + } + break; } - if ((mOldLocalPrefix.GetLength() != 0) && (now >= mOldExpireTime)) + for (OldPrefix &entry : mOldLocalPrefixes) { - Ip6::Prefix oldPrefix = mOldLocalPrefix; - - LogInfo("Old local on-link prefix %s expired", mOldLocalPrefix.ToString().AsCString()); - mOldLocalPrefix.Clear(); - Get().EvaluatePublishingPrefix(oldPrefix); + if (now >= entry.mExpireTime) + { + SuccessOrAssert(expiredPrefixes.PushBack(entry.mPrefix)); + } + else + { + nextExpireTime = Min(nextExpireTime, entry.mExpireTime); + } } - // Re-schedule the timer - - if (mState == kDeprecating) + for (const Ip6::Prefix &prefix : expiredPrefixes) { - mTimer.FireAt(mExpireTime); + LogInfo("Old local on-link prefix %s expired", prefix.ToString().AsCString()); + mOldLocalPrefixes.RemoveMatching(prefix); + Get().EvaluatePublishingPrefix(prefix); } - if (mOldLocalPrefix.GetLength() != 0) + if (nextExpireTime != now.GetDistantFuture()) { - mTimer.FireAtIfEarlier(mOldExpireTime); + mTimer.FireAtIfEarlier(nextExpireTime); } } diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index 85ddcfb8a..2ec5ebb04 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -93,12 +93,13 @@ public: * * The number of published entries accounts for: * - Max number of discovered prefix entries, - * - Two entries for local on-link prefixes (current prefix and old one deprecating on extended PAN ID change), + * - One entry for local on-link prefixes, + * - Max number of old (deprecating) local on-link prefixes, * - One entry for NAT64 published prefix. * */ - static constexpr uint16_t kMaxPublishedPrefixes = OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_DISCOVERED_PREFIXES + 3; - + static constexpr uint16_t kMaxPublishedPrefixes = OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_DISCOVERED_PREFIXES + 1 + + OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_OLD_ON_LINK_PREFIXES + 1; /** * This constructor initializes the routing manager. * @@ -640,6 +641,9 @@ private: public: explicit OnLinkPrefixManager(Instance &aInstance); + // Max number of old on-link prefixes to retain to deprecate. + static constexpr uint16_t kMaxOldPrefixes = OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_OLD_ON_LINK_PREFIXES; + void GenerateLocalPrefix(void); void Start(void); void Stop(void); @@ -655,7 +659,7 @@ private: void HandleTimer(void); private: - enum State : uint8_t + enum State : uint8_t // State of `mLocalPrefix` { kIdle, kPublishing, @@ -663,21 +667,30 @@ private: kDeprecating, }; + struct OldPrefix + { + bool Matches(const Ip6::Prefix &aPrefix) const { return mPrefix == aPrefix; } + + Ip6::Prefix mPrefix; + TimeMilli mExpireTime; + }; + void PublishAndAdvertise(void); void Deprecate(void); + void ResetExpireTime(TimeMilli aNow); void EnterAdvertisingState(void); void AppendCurPrefix(Ip6::Nd::RouterAdvertMessage &aRaMessage); - void AppendOldPrefix(Ip6::Nd::RouterAdvertMessage &aRaMessage); + void AppendOldPrefixes(Ip6::Nd::RouterAdvertMessage &aRaMessage); + void DeprecateOldPrefix(const Ip6::Prefix &aPrefix, TimeMilli aExpireTime); using ExpireTimer = TimerMilliIn; - Ip6::Prefix mLocalPrefix; - Ip6::Prefix mFavoredDiscoveredPrefix; - State mState; - TimeMilli mExpireTime; - Ip6::Prefix mOldLocalPrefix; - TimeMilli mOldExpireTime; - ExpireTimer mTimer; + Ip6::Prefix mLocalPrefix; + State mState; + TimeMilli mExpireTime; + Ip6::Prefix mFavoredDiscoveredPrefix; + Array mOldLocalPrefixes; + ExpireTimer mTimer; }; typedef Ip6::Prefix OnMeshPrefix; diff --git a/src/core/config/border_routing.h b/src/core/config/border_routing.h index b76fbeaf2..bc3131b47 100644 --- a/src/core/config/border_routing.h +++ b/src/core/config/border_routing.h @@ -68,7 +68,7 @@ /** * @def OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_ON_MESH_PREFIXES * - * Specified maximum number of on-mesh prefixes (discovered from Thread Network Data) that are included as Route Info + * Specifies maximum number of on-mesh prefixes (discovered from Thread Network Data) that are included as Route Info * Option in emitted Router Advertisement messages. * */ @@ -76,4 +76,13 @@ #define OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_ON_MESH_PREFIXES 16 #endif +/** + * @def OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_OLD_ON_LINK_PREFIXES + * + * Specifies maximum number of old local on-link prefixes (being deprecated) maintained by routing manager. + * + */ +#ifndef OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_OLD_ON_LINK_PREFIXES +#define OPENTHREAD_CONFIG_BORDER_ROUTING_MAX_OLD_ON_LINK_PREFIXES 3 +#endif #endif // CONFIG_BORDER_ROUTING_H_ diff --git a/tests/unit/test_routing_manager.cpp b/tests/unit/test_routing_manager.cpp index a70f3f264..c9db297db 100644 --- a/tests/unit/test_routing_manager.cpp +++ b/tests/unit/test_routing_manager.cpp @@ -46,8 +46,8 @@ using namespace ot; // Logs a message and adds current time (sNow) as "::." -#define Log(...) \ - printf("%02u:%02u:%02u.%03u " OT_FIRST_ARG(__VA_ARGS__) "\n", (sNow / 36000000), (sNow / 60000) % 60, \ +#define Log(...) \ + printf("%02u:%02u:%02u.%03u " OT_FIRST_ARG(__VA_ARGS__) "\n", (sNow / 3600000), (sNow / 60000) % 60, \ (sNow / 1000) % 60, sNow % 1000 OT_REST_ARGS(__VA_ARGS__)) static constexpr uint32_t kInfraIfIndex = 1; @@ -56,7 +56,8 @@ static const char kInfraIfAddress[] = "fe80::1"; static constexpr uint32_t kValidLitime = 2000; static constexpr uint32_t kPreferredLifetime = 1800; -static constexpr uint16_t kMaxRaSize = 800; +static constexpr uint16_t kMaxRaSize = 800; +static constexpr uint16_t kMaxDeprecatingPrefixes = 16; static ot::Instance *sInstance; @@ -77,15 +78,31 @@ enum ExpectedPio kPioDeprecatingLocalOnLink, // Expect to see local on-link prefix deprecated (zero preferred lifetime). }; +struct DeprecatingPrefix +{ + DeprecatingPrefix(void) = default; + + DeprecatingPrefix(const Ip6::Prefix &aPrefix, uint32_t aLifetime) + : mPrefix(aPrefix) + , mLifetime(aLifetime) + { + } + + bool Matches(const Ip6::Prefix &aPrefix) const { return mPrefix == aPrefix; } + + Ip6::Prefix mPrefix; // Old on-link prefix being deprecated. + uint32_t mLifetime; // Valid lifetime of prefix from PIO. +}; + static Ip6::Address sInfraIfAddress; -bool sRsEmitted; // Indicates if an RS message was emitted by BR. -bool sRaValidated; // Indicates if an RA was emitted by BR and successfully validated. -ExpectedPio sExpectedPio; // Expected PIO in the emitted RA by BR (MUST be seen in RA to set `sRaValidated`). -bool sExpectOldOnLinkPio; // Expect to see old local prefix PIO -uint32_t sOnLinkLifetime; // Valid lifetime for local on-link prefix from the last processed RA. -uint32_t sOldOnLinkLifetime; // Valid lifetime of the old local prefix PIO (when `sExpectOldOnLinkPio`). -Ip6::Prefix sOldOnLinkPrefix; // The old on-link PIO prefix in last processed RA (when `sExpectOldOnLinkPio`) +bool sRsEmitted; // Indicates if an RS message was emitted by BR. +bool sRaValidated; // Indicates if an RA was emitted by BR and successfully validated. +ExpectedPio sExpectedPio; // Expected PIO in the emitted RA by BR (MUST be seen in RA to set `sRaValidated`). +uint32_t sOnLinkLifetime; // Valid lifetime for local on-link prefix from the last processed RA. + +// Array containing deprecating prefixes from PIOs in the last processed RA. +Array sDeprecatingPrefixes; static constexpr uint16_t kMaxRioPrefixes = 10; @@ -139,6 +156,22 @@ void ValidateRouterAdvert(const Icmp6Packet &aPacket); const char *PreferenceToString(int8_t aPreference); void SendRouterAdvert(const Ip6::Address &aAddress, const Icmp6Packet &aPacket); +#if OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_PLATFORM_DEFINED +void otPlatLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFormat, ...) +{ + OT_UNUSED_VARIABLE(aLogLevel); + OT_UNUSED_VARIABLE(aLogRegion); + + va_list args; + + printf(" "); + va_start(args, aFormat); + vprintf(aFormat, args); + va_end(args); + printf("\n"); +} +#endif + //---------------------------------------------------------------------------------------------------------------------- // `otPlatRadio @@ -260,11 +293,12 @@ void AdvanceTime(uint32_t aDuration) void ValidateRouterAdvert(const Icmp6Packet &aPacket) { Ip6::Nd::RouterAdvertMessage raMsg(aPacket); - bool sawExpectedPio = false; - bool sawExpecteOldPio = false; + bool sawExpectedPio = false; VerifyOrQuit(raMsg.IsValid()); + sDeprecatingPrefixes.Clear(); + for (const Ip6::Nd::Option &option : raMsg) { switch (option.GetType()) @@ -288,28 +322,26 @@ void ValidateRouterAdvert(const Icmp6Packet &aPacket) break; case kPioAdvertisingLocalOnLink: - VerifyOrQuit(pio.GetPreferredLifetime() > 0, "On link prefix is deprecated unexpectedly"); - sOnLinkLifetime = pio.GetValidLifetime(); - sawExpectedPio = true; + if (pio.GetPreferredLifetime() > 0) + { + sOnLinkLifetime = pio.GetValidLifetime(); + sawExpectedPio = true; + } break; case kPioDeprecatingLocalOnLink: - VerifyOrQuit(pio.GetPreferredLifetime() == 0, "On link prefix is not deprecated"); - sOnLinkLifetime = pio.GetValidLifetime(); - sawExpectedPio = true; + if (pio.GetPreferredLifetime() == 0) + { + sOnLinkLifetime = pio.GetValidLifetime(); + sawExpectedPio = true; + } break; } } - else if (sExpectOldOnLinkPio) + else { VerifyOrQuit(pio.GetPreferredLifetime() == 0, "Old on link prefix is not deprecated"); - sOldOnLinkPrefix = prefix; - sOldOnLinkLifetime = pio.GetValidLifetime(); - - if (sExpectOldOnLinkPio) - { - sawExpecteOldPio = true; - } + SuccessOrQuit(sDeprecatingPrefixes.PushBack(DeprecatingPrefix(prefix, pio.GetValidLifetime()))); } break; } @@ -354,11 +386,6 @@ void ValidateRouterAdvert(const Icmp6Packet &aPacket) break; } - if (sExpectOldOnLinkPio) - { - VerifyOrExit(sawExpecteOldPio); - } - sRaValidated = true; } @@ -477,6 +504,15 @@ void VerifyOmrPrefixInNetData(const Ip6::Prefix &aOmrPrefix, bool aDefaultRoute VerifyOrQuit(otNetDataGetNextOnMeshPrefix(sInstance, &iterator, &prefixConfig) == kErrorNotFound); } +void VerifyNoOmrPrefixInNetData(void) +{ + otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT; + NetworkData::OnMeshPrefixConfig prefixConfig; + + Log("VerifyNoOmrPrefixInNetData()"); + VerifyOrQuit(otNetDataGetNextOnMeshPrefix(sInstance, &iterator, &prefixConfig) != kErrorNone); +} + using NetworkData::RoutePreference; struct ExternalRoute @@ -526,6 +562,15 @@ template void VerifyExternalRoutesInNetData(const ExternalRou VerifyOrQuit(counter == kLength); } +void VerifyNoExternalRouteInNetData(void) +{ + otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT; + NetworkData::ExternalRouteConfig routeConfig; + + Log("VerifyNoExternalRouteInNetData()"); + VerifyOrQuit(otNetDataGetNextRoute(sInstance, &iterator, &routeConfig) != kErrorNone); +} + struct Pio { Pio(const Ip6::Prefix &aPrefix, uint32_t aValidLifetime, uint32_t aPreferredLifetime) @@ -1495,7 +1540,9 @@ void TestExtPanIdChange(void) static const otExtendedPanId kExtPanId1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x6, 0x7, 0x08}}; static const otExtendedPanId kExtPanId2 = {{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x99, 0x88}}; - static const otExtendedPanId kExtPanId3 = {{0x12, 0x34, 056, 0x78, 0x9a, 0xab, 0xcd, 0xef}}; + static const otExtendedPanId kExtPanId3 = {{0x12, 0x34, 0x56, 0x78, 0x9a, 0xab, 0xcd, 0xef}}; + static const otExtendedPanId kExtPanId4 = {{0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00}}; + static const otExtendedPanId kExtPanId5 = {{0x77, 0x88, 0x00, 0x00, 0x55, 0x55, 0x55, 0x55}}; Ip6::Prefix localOnLink; Ip6::Prefix oldLocalOnLink; @@ -1503,6 +1550,7 @@ void TestExtPanIdChange(void) Ip6::Prefix onLinkPrefix = PrefixFromString("2000:abba:baba::", 64); Ip6::Address routerAddressA = AddressFromString("fd00::aaaa"); uint32_t oldPrefixLifetime; + Ip6::Prefix oldPrefixes[4]; otOperationalDataset dataset; Log("--------------------------------------------------------------------------------------------"); @@ -1546,9 +1594,8 @@ void TestExtPanIdChange(void) oldLocalOnLink = localOnLink; oldPrefixLifetime = sOnLinkLifetime; - sRaValidated = false; - sExpectOldOnLinkPio = true; - sExpectedPio = kPioAdvertisingLocalOnLink; + sRaValidated = false; + sExpectedPio = kPioAdvertisingLocalOnLink; SuccessOrQuit(otDatasetGetActive(sInstance, &dataset)); @@ -1569,7 +1616,9 @@ void TestExtPanIdChange(void) AdvanceTime(30000); VerifyOrQuit(sRaValidated); - VerifyOrQuit(sOldOnLinkPrefix == oldLocalOnLink); + VerifyOrQuit(sDeprecatingPrefixes.GetLength() == 1); + VerifyOrQuit(sDeprecatingPrefixes[0].mPrefix == oldLocalOnLink); + oldPrefixLifetime = sDeprecatingPrefixes[0].mLifetime; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Validate the Network Data to contain both the current and old @@ -1580,46 +1629,47 @@ void TestExtPanIdChange(void) ExternalRoute(oldLocalOnLink, NetworkData::kRoutePreferenceMedium)}); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Change the extended PAN ID again. + // Stop BR and validate that a final RA is emitted deprecating + // both current local on-link prefix and old prefix. - Log("Changing ext PAN ID again"); + sRaValidated = false; + sExpectedPio = kPioDeprecatingLocalOnLink; - oldLocalOnLink = localOnLink; - oldPrefixLifetime = sOnLinkLifetime; - - sRaValidated = false; - sExpectOldOnLinkPio = true; - sExpectedPio = kPioAdvertisingLocalOnLink; - - dataset.mExtendedPanId = kExtPanId3; - SuccessOrQuit(otDatasetSetActive(sInstance, &dataset)); - - AdvanceTime(500); - SuccessOrQuit(sInstance->Get().GetOnLinkPrefix(localOnLink)); - Log("Local on-link prefix changed to %s from %s", localOnLink.ToString().AsCString(), - oldLocalOnLink.ToString().AsCString()); - - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Validate the received RA message and that it contains the - // old on-link prefix being deprecated. - - AdvanceTime(30000); + SuccessOrQuit(sInstance->Get().SetEnabled(false)); + AdvanceTime(100); VerifyOrQuit(sRaValidated); - VerifyOrQuit(sOldOnLinkPrefix == oldLocalOnLink); + VerifyOrQuit(sDeprecatingPrefixes.GetLength() == 1); + VerifyOrQuit(sDeprecatingPrefixes[0].mPrefix == oldLocalOnLink); + oldPrefixLifetime = sDeprecatingPrefixes[0].mLifetime; + + sRaValidated = false; + AdvanceTime(350000); + VerifyOrQuit(!sRaValidated); + + VerifyNoOmrPrefixInNetData(); + VerifyNoExternalRouteInNetData(); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Validate the Network Data to contain both the current and old - // local on-link prefixes and the previous old prefix is now removed. + // Start BR again and validate old prefix will continue to + // be deprecated. - VerifyOmrPrefixInNetData(localOmr); - VerifyExternalRoutesInNetData({ExternalRoute(localOnLink, NetworkData::kRoutePreferenceMedium), - ExternalRoute(oldLocalOnLink, NetworkData::kRoutePreferenceMedium)}); + sRaValidated = false; + sExpectedPio = kPioAdvertisingLocalOnLink; + + SuccessOrQuit(sInstance->Get().SetEnabled(true)); + + AdvanceTime(300000); + VerifyOrQuit(sRaValidated); + VerifyOrQuit(sDeprecatingPrefixes.GetLength() == 1); + VerifyOrQuit(sDeprecatingPrefixes[0].mPrefix == oldLocalOnLink); + VerifyOrQuit(oldPrefixLifetime > sDeprecatingPrefixes[0].mLifetime); + oldPrefixLifetime = sDeprecatingPrefixes[0].mLifetime; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Wait for old local on-link prefix to expire. - while (oldPrefixLifetime > kMaxRaTxInterval) + while (oldPrefixLifetime > 2 * kMaxRaTxInterval) { // Ensure Network Data entries remain as before. Mainly we still // see the deprecating local on-link prefix. @@ -1635,21 +1685,22 @@ void TestExtPanIdChange(void) AdvanceTime(kMaxRaTxInterval * 1000); VerifyOrQuit(sRaValidated); - Log("Old on-link prefix is deprecating, remaining lifetime:%d", sOldOnLinkLifetime); - VerifyOrQuit(sOldOnLinkLifetime < oldPrefixLifetime); - oldPrefixLifetime = sOldOnLinkLifetime; + VerifyOrQuit(sDeprecatingPrefixes.GetLength() == 1); + Log("Old on-link prefix is deprecating, remaining lifetime:%d", sDeprecatingPrefixes[0].mLifetime); + VerifyOrQuit(sDeprecatingPrefixes[0].mLifetime < oldPrefixLifetime); + oldPrefixLifetime = sDeprecatingPrefixes[0].mLifetime; } //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // The local on-link prefix must be expired now and should no // longer be seen in the emitted RA message. - sRaValidated = false; - sExpectOldOnLinkPio = false; + sRaValidated = false; - AdvanceTime(kMaxRaTxInterval * 1000); + AdvanceTime(2 * kMaxRaTxInterval * 1000); VerifyOrQuit(sRaValidated); + VerifyOrQuit(sDeprecatingPrefixes.IsEmpty()); Log("Old on-link prefix is now expired"); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1672,9 +1723,8 @@ void TestExtPanIdChange(void) //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Validate that the local on-link prefix is deprecated. - sRaValidated = false; - sExpectOldOnLinkPio = false; - sExpectedPio = kPioDeprecatingLocalOnLink; + sRaValidated = false; + sExpectedPio = kPioDeprecatingLocalOnLink; AdvanceTime(30000); @@ -1698,13 +1748,14 @@ void TestExtPanIdChange(void) // Validate that the old local on-link prefix is still being included // as PIO in the emitted RA. - sRaValidated = false; - sExpectOldOnLinkPio = true; - sExpectedPio = kNoPio; + sRaValidated = false; + sExpectedPio = kNoPio; AdvanceTime(30000); VerifyOrQuit(sRaValidated); + VerifyOrQuit(sDeprecatingPrefixes.GetLength() == 1); + VerifyOrQuit(sDeprecatingPrefixes[0].mPrefix == oldLocalOnLink); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Validate that Network Data contains the old local on-link @@ -1717,7 +1768,7 @@ void TestExtPanIdChange(void) //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Wait for old local on-link prefix to expire. - while (oldPrefixLifetime > kMaxRaTxInterval) + while (oldPrefixLifetime > 2 * kMaxRaTxInterval) { // Send same RA from router A to keep its on-link prefix alive. @@ -1737,29 +1788,266 @@ void TestExtPanIdChange(void) AdvanceTime(kMaxRaTxInterval * 1000); VerifyOrQuit(sRaValidated); - Log("Old on-link prefix is deprecating, remaining lifetime:%d", sOldOnLinkLifetime); - VerifyOrQuit(sOldOnLinkLifetime < oldPrefixLifetime); - oldPrefixLifetime = sOldOnLinkLifetime; + VerifyOrQuit(sDeprecatingPrefixes.GetLength() == 1); + Log("Old on-link prefix is deprecating, remaining lifetime:%d", sDeprecatingPrefixes[0].mLifetime); + VerifyOrQuit(sDeprecatingPrefixes[0].mLifetime < oldPrefixLifetime); + oldPrefixLifetime = sDeprecatingPrefixes[0].mLifetime; } //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // The old on-link prefix must be expired now and should no // longer be seen in the emitted RA message. - sRaValidated = false; - sExpectOldOnLinkPio = false; + SendRouterAdvert(routerAddressA, {Pio(onLinkPrefix, kValidLitime, kPreferredLifetime)}); + sRaValidated = false; - AdvanceTime(kMaxRaTxInterval * 1000); + AdvanceTime(2 * kMaxRaTxInterval * 1000); VerifyOrQuit(sRaValidated); + VerifyOrQuit(sDeprecatingPrefixes.IsEmpty()); Log("Old on-link prefix is now expired"); //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Validate the Network Data to now only contains entry from router A + // Validate the Network Data to now only contains entry from router A. VerifyOmrPrefixInNetData(localOmr); VerifyExternalRoutesInNetData({ExternalRoute(onLinkPrefix, NetworkData::kRoutePreferenceMedium)}); + //= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + // Check behavior when ext PAN ID changes while the local on-link is not + // advertised. + + Log("Changing ext PAN ID again"); + + oldLocalOnLink = localOnLink; + + sRaValidated = false; + sExpectedPio = kNoPio; + + dataset.mExtendedPanId = kExtPanId3; + SuccessOrQuit(otDatasetSetActive(sInstance, &dataset)); + + AdvanceTime(500); + SuccessOrQuit(sInstance->Get().GetOnLinkPrefix(localOnLink)); + Log("Local on-link prefix changed to %s from %s", localOnLink.ToString().AsCString(), + oldLocalOnLink.ToString().AsCString()); + + AdvanceTime(35000); + VerifyOrQuit(sRaValidated); + VerifyOrQuit(sDeprecatingPrefixes.IsEmpty()); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Validate the Network Data to now only contains entry from router A. + + VerifyOmrPrefixInNetData(localOmr); + VerifyExternalRoutesInNetData({ExternalRoute(onLinkPrefix, NetworkData::kRoutePreferenceMedium)}); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Remove the on-link prefix PIO being advertised by router A + // and ensure local on-link prefix is advertised again. + + sRaValidated = false; + sExpectedPio = kPioAdvertisingLocalOnLink; + + SendRouterAdvert(routerAddressA, {Pio(onLinkPrefix, kValidLitime, 0)}); + + AdvanceTime(300000); + VerifyOrQuit(sRaValidated); + VerifyOrQuit(sDeprecatingPrefixes.IsEmpty()); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Wait for longer than valid lifetime of PIO entry from router A. + // Validate that it is unpublished from network data. + + AdvanceTime(2000 * 1000); + VerifyOmrPrefixInNetData(localOmr); + VerifyExternalRoutesInNetData({ExternalRoute(localOnLink, NetworkData::kRoutePreferenceMedium)}); + + //= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + // Multiple PAN ID changes and multiple deprecating old prefixes. + + oldPrefixes[0] = localOnLink; + + dataset.mExtendedPanId = kExtPanId2; + SuccessOrQuit(otDatasetSetActive(sInstance, &dataset)); + + sRaValidated = false; + sExpectedPio = kPioAdvertisingLocalOnLink; + + AdvanceTime(30000); + SuccessOrQuit(sInstance->Get().GetOnLinkPrefix(localOnLink)); + VerifyOrQuit(sRaValidated); + VerifyOrQuit(sDeprecatingPrefixes.GetLength() == 1); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[0])); + + VerifyExternalRoutesInNetData({ExternalRoute(localOnLink, NetworkData::kRoutePreferenceMedium), + ExternalRoute(oldPrefixes[0], NetworkData::kRoutePreferenceMedium)}); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Change the prefix again. We should see two deprecating prefixes. + + oldPrefixes[1] = localOnLink; + + dataset.mExtendedPanId = kExtPanId1; + SuccessOrQuit(otDatasetSetActive(sInstance, &dataset)); + + sRaValidated = false; + sExpectedPio = kPioAdvertisingLocalOnLink; + + AdvanceTime(30000); + SuccessOrQuit(sInstance->Get().GetOnLinkPrefix(localOnLink)); + VerifyOrQuit(sRaValidated); + VerifyOrQuit(sDeprecatingPrefixes.GetLength() == 2); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[0])); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[1])); + + VerifyExternalRoutesInNetData({ExternalRoute(localOnLink, NetworkData::kRoutePreferenceMedium), + ExternalRoute(oldPrefixes[0], NetworkData::kRoutePreferenceMedium), + ExternalRoute(oldPrefixes[1], NetworkData::kRoutePreferenceMedium)}); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Wait for 15 minutes and then change ext PAN ID again. + // Now we should see three deprecating prefixes. + + AdvanceTime(15 * 60 * 1000); + + oldPrefixes[2] = localOnLink; + + dataset.mExtendedPanId = kExtPanId4; + SuccessOrQuit(otDatasetSetActive(sInstance, &dataset)); + + sRaValidated = false; + sExpectedPio = kPioAdvertisingLocalOnLink; + + AdvanceTime(30000); + SuccessOrQuit(sInstance->Get().GetOnLinkPrefix(localOnLink)); + VerifyOrQuit(sRaValidated); + VerifyOrQuit(sDeprecatingPrefixes.GetLength() == 3); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[0])); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[1])); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[2])); + + VerifyExternalRoutesInNetData({ExternalRoute(localOnLink, NetworkData::kRoutePreferenceMedium), + ExternalRoute(oldPrefixes[0], NetworkData::kRoutePreferenceMedium), + ExternalRoute(oldPrefixes[1], NetworkData::kRoutePreferenceMedium), + ExternalRoute(oldPrefixes[2], NetworkData::kRoutePreferenceMedium)}); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Change ext PAN ID back to previous value of `kExtPanId1`. + // We should still see three deprecating prefixes and the last prefix + // at `oldPrefixes[2]` should again be treated as local on-link prefix. + + oldPrefixes[3] = localOnLink; + + dataset.mExtendedPanId = kExtPanId1; + SuccessOrQuit(otDatasetSetActive(sInstance, &dataset)); + + sRaValidated = false; + sExpectedPio = kPioAdvertisingLocalOnLink; + + AdvanceTime(30000); + SuccessOrQuit(sInstance->Get().GetOnLinkPrefix(localOnLink)); + VerifyOrQuit(sRaValidated); + VerifyOrQuit(sDeprecatingPrefixes.GetLength() == 3); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[0])); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[1])); + VerifyOrQuit(oldPrefixes[2] == localOnLink); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[3])); + + VerifyExternalRoutesInNetData({ExternalRoute(localOnLink, NetworkData::kRoutePreferenceMedium), + ExternalRoute(oldPrefixes[0], NetworkData::kRoutePreferenceMedium), + ExternalRoute(oldPrefixes[1], NetworkData::kRoutePreferenceMedium), + ExternalRoute(oldPrefixes[3], NetworkData::kRoutePreferenceMedium)}); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Stop BR and validate the final emitted RA to contain + // all deprecating prefixes. + + sRaValidated = false; + sExpectedPio = kPioDeprecatingLocalOnLink; + + SuccessOrQuit(sInstance->Get().SetEnabled(false)); + AdvanceTime(100); + + VerifyOrQuit(sRaValidated); + VerifyOrQuit(sDeprecatingPrefixes.GetLength() == 3); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[0])); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[1])); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[3])); + + VerifyNoOmrPrefixInNetData(); + VerifyNoExternalRouteInNetData(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Wait for 15 minutes while BR stays disabled and validate + // there are no emitted RAs. We want to check that deprecating + // prefixes continue to expire while BR is stopped. + + sRaValidated = false; + AdvanceTime(15 * 60 * 1000); + + VerifyOrQuit(!sRaValidated); + + VerifyNoOmrPrefixInNetData(); + VerifyNoExternalRouteInNetData(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start BR again, and check that we only see the last deprecating prefix + // at `oldPrefixes[3]` in emitted RA and the other two are expired and + // no longer included as PIO and/or in network data. + + sRaValidated = false; + sExpectedPio = kPioAdvertisingLocalOnLink; + + SuccessOrQuit(sInstance->Get().SetEnabled(true)); + + AdvanceTime(30000); + + VerifyOrQuit(sRaValidated); + VerifyOrQuit(sDeprecatingPrefixes.GetLength() == 1); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[3])); + + VerifyExternalRoutesInNetData({ExternalRoute(localOnLink, NetworkData::kRoutePreferenceMedium), + ExternalRoute(oldPrefixes[3], NetworkData::kRoutePreferenceMedium)}); + + //= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + // Validate the oldest prefix is removed when we have too many + // back-to-back PAN ID changes. + + // Remember the oldest deprecating prefix (associated with `kExtPanId4`). + oldLocalOnLink = oldPrefixes[3]; + + SuccessOrQuit(sInstance->Get().GetOnLinkPrefix(oldPrefixes[0])); + dataset.mExtendedPanId = kExtPanId2; + SuccessOrQuit(otDatasetSetActive(sInstance, &dataset)); + AdvanceTime(30000); + + SuccessOrQuit(sInstance->Get().GetOnLinkPrefix(oldPrefixes[1])); + dataset.mExtendedPanId = kExtPanId3; + SuccessOrQuit(otDatasetSetActive(sInstance, &dataset)); + AdvanceTime(30000); + + SuccessOrQuit(sInstance->Get().GetOnLinkPrefix(oldPrefixes[2])); + dataset.mExtendedPanId = kExtPanId5; + SuccessOrQuit(otDatasetSetActive(sInstance, &dataset)); + + sRaValidated = false; + + AdvanceTime(30000); + + VerifyOrQuit(sRaValidated); + SuccessOrQuit(sInstance->Get().GetOnLinkPrefix(localOnLink)); + VerifyOrQuit(sDeprecatingPrefixes.GetLength() == 3); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[0])); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[1])); + VerifyOrQuit(sDeprecatingPrefixes.ContainsMatching(oldPrefixes[2])); + VerifyOrQuit(!sDeprecatingPrefixes.ContainsMatching(oldLocalOnLink)); + + VerifyExternalRoutesInNetData({ExternalRoute(localOnLink, NetworkData::kRoutePreferenceMedium), + ExternalRoute(oldPrefixes[0], NetworkData::kRoutePreferenceMedium), + ExternalRoute(oldPrefixes[1], NetworkData::kRoutePreferenceMedium), + ExternalRoute(oldPrefixes[2], NetworkData::kRoutePreferenceMedium)}); + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("End of TestExtPanIdChange");