diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index c8b3ade099a..7e8a08947ff 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -620,6 +620,12 @@ void RoutingManager::SendRouterAdvertisement(RouterAdvTxMode aRaTxMode) LogInfo("Preparing RA"); + mDiscoveredPrefixTable.DetermineAndSetFlags(raMsg); + + LogInfo("- RA Header - flags - M:%u O:%u", raMsg.GetHeader().IsManagedAddressConfigFlagSet(), + raMsg.GetHeader().IsOtherConfigFlagSet()); + LogInfo("- RA Header - default route - lifetime:%u", raMsg.GetHeader().GetRouterLifetime()); + #if OPENTHREAD_CONFIG_BORDER_ROUTING_STUB_ROUTER_FLAG_IN_EMITTED_RA_ENABLE SuccessOrAssert(raMsg.AppendFlagsExtensionOption(/* aStubRouterFlag */ true)); LogInfo("- FlagsExt - StubRouter:1"); @@ -1242,7 +1248,7 @@ void RoutingManager::DiscoveredPrefixTable::ProcessRouterAdvertMessage(const Ip6 UpdateRouterOnRx(*router); - RemoveRoutersWithNoEntries(); + RemoveRoutersWithNoEntriesOrFlags(); exit: return; @@ -1472,7 +1478,7 @@ void RoutingManager::DiscoveredPrefixTable::RemovePrefix(const Entry::Matcher &a VerifyOrExit(!removedEntries.IsEmpty()); FreeEntries(removedEntries); - RemoveRoutersWithNoEntries(); + RemoveRoutersWithNoEntriesOrFlags(); SignalTableChanged(); @@ -1482,21 +1488,17 @@ void RoutingManager::DiscoveredPrefixTable::RemovePrefix(const Entry::Matcher &a void RoutingManager::DiscoveredPrefixTable::RemoveAllEntries(void) { - // Remove all entries from the table and unpublish them - // from Network Data. + // Remove all entries from the table. for (Router &router : mRouters) { - if (!router.mEntries.IsEmpty()) - { - SignalTableChanged(); - } - FreeEntries(router.mEntries); } - RemoveRoutersWithNoEntries(); + FreeRouters(mRouters); mEntryTimer.Stop(); + + SignalTableChanged(); } void RoutingManager::DiscoveredPrefixTable::RemoveOrDeprecateOldEntries(TimeMilli aTimeThreshold) @@ -1592,11 +1594,11 @@ TimeMilli RoutingManager::DiscoveredPrefixTable::CalculateNextStaleTime(TimeMill return foundOnLink ? Min(onLinkStaleTime, routeStaleTime) : routeStaleTime; } -void RoutingManager::DiscoveredPrefixTable::RemoveRoutersWithNoEntries(void) +void RoutingManager::DiscoveredPrefixTable::RemoveRoutersWithNoEntriesOrFlags(void) { LinkedList routersToFree; - mRouters.RemoveAllMatching(Router::kContainsNoEntries, routersToFree); + mRouters.RemoveAllMatching(Router::kContainsNoEntriesOrFlags, routersToFree); FreeRouters(routersToFree); } @@ -1668,7 +1670,7 @@ void RoutingManager::DiscoveredPrefixTable::RemoveExpiredEntries(void) router.mEntries.RemoveAllMatching(Entry::ExpirationChecker(now), expiredEntries); } - RemoveRoutersWithNoEntries(); + RemoveRoutersWithNoEntriesOrFlags(); if (!expiredEntries.IsEmpty()) { @@ -1792,6 +1794,42 @@ void RoutingManager::DiscoveredPrefixTable::SendNeighborSolicitToRouter(const Ro return; } +void RoutingManager::DiscoveredPrefixTable::DetermineAndSetFlags(Ip6::Nd::RouterAdvertMessage &aRaMessage) const +{ + // Determine the `M` and `O` flags to include in the RA message + // header `aRaMessage` 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. + // + // If a router has failed to respond to max number of NS probe + // attempts, we consider it as offline and ignore its flags. + + for (const Router &router : mRouters) + { + if (router.mStubRouterFlag) + { + continue; + } + + if (router.mNsProbeCount > Router::kMaxNsProbes) + { + continue; + } + + if (router.mManagedAddressConfigFlag) + { + aRaMessage.GetHeader().SetManagedAddressConfigFlag(); + } + + if (router.mOtherConfigFlag) + { + aRaMessage.GetHeader().SetOtherConfigFlag(); + } + } +} + void RoutingManager::DiscoveredPrefixTable::InitIterator(PrefixTableIterator &aIterator) const { static_cast(aIterator).Init(mRouters); @@ -1995,6 +2033,27 @@ uint32_t RoutingManager::DiscoveredPrefixTable::Entry::CalculateExpireDelay(uint //--------------------------------------------------------------------------------------------------------------------- // DiscoveredPrefixTable::Router +bool RoutingManager::DiscoveredPrefixTable::Router::Matches(EmptyChecker aChecker) const +{ + // Checks whether or not a `Router` instance has any useful info. An + // entry can be removed if it does not advertise M or O flags and + // also does not have any advertised prefix entries (RIO/PIO). 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. + + OT_UNUSED_VARIABLE(aChecker); + + bool hasFlags = false; + + if (mNsProbeCount <= kMaxNsProbes) + { + hasFlags = (mManagedAddressConfigFlag || mOtherConfigFlag); + } + + return !hasFlags && mEntries.IsEmpty(); +} + void RoutingManager::DiscoveredPrefixTable::Router::CopyInfoTo(RouterEntry &aEntry) const { aEntry.mAddress = mAddress; diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index db43f96a02e..f0d824e8218 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -635,6 +635,8 @@ class RoutingManager : public InstanceLocator TimeMilli CalculateNextStaleTime(TimeMilli aNow) const; + void DetermineAndSetFlags(Ip6::Nd::RouterAdvertMessage &aRaMessage) const; + void InitIterator(PrefixTableIterator &aIterator) const; Error GetNextEntry(PrefixTableIterator &aIterator, PrefixTableEntry &aEntry) const; Error GetNextRouter(PrefixTableIterator &aIterator, RouterEntry &aEntry) const; @@ -770,11 +772,11 @@ class RoutingManager : public InstanceLocator enum EmptyChecker : uint8_t { - kContainsNoEntries + kContainsNoEntriesOrFlags }; bool Matches(const Ip6::Address &aAddress) const { return aAddress == mAddress; } - bool Matches(EmptyChecker) const { return mEntries.IsEmpty(); } + bool Matches(EmptyChecker aChecker) const; void CopyInfoTo(RouterEntry &aEntry) const; Router *mNext; @@ -816,7 +818,7 @@ class RoutingManager : public InstanceLocator bool Contains(const Entry::Checker &aChecker) const; void RemovePrefix(const Entry::Matcher &aMatcher); void RemoveOrDeprecateEntriesFromInactiveRouters(void); - void RemoveRoutersWithNoEntries(void); + void RemoveRoutersWithNoEntriesOrFlags(void); void FreeRouters(LinkedList &aRouters); void FreeEntries(LinkedList &aEntries); void UpdateNetworkDataOnChangeTo(Entry &aEntry); diff --git a/src/core/net/nd6.hpp b/src/core/net/nd6.hpp index c5cf766d91d..f4a6d5a25d0 100644 --- a/src/core/net/nd6.hpp +++ b/src/core/net/nd6.hpp @@ -733,6 +733,14 @@ class RouterAdvertMessage */ const Header &GetHeader(void) const { return *reinterpret_cast(mData.GetBytes()); } + /** + * Gets the RA message's header. + * + * @returns The RA message's header. + * + */ + Header &GetHeader(void) { return *reinterpret_cast
(AsNonConst(mData.GetBytes())); } + /** * Appends a Prefix Info Option to the RA message. * diff --git a/tests/unit/test_routing_manager.cpp b/tests/unit/test_routing_manager.cpp index b4982c03005..ef2a53bf17a 100644 --- a/tests/unit/test_routing_manager.cpp +++ b/tests/unit/test_routing_manager.cpp @@ -155,6 +155,18 @@ bool sRespondToNs; // Indicates whether or not to respond to NS. 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. +enum ExpectedRaHeaderFlags +{ + kRaHeaderFlagsSkipChecking, // Skip checking the RA header flags. + kRaHeaderFlagsNone, // Expect no flag (neither M or O). + kRaHeaderFlagsOnlyM, // Expect M flag only. + kRaHeaderFlagsOnlyO, // Expect O flag only. + kRaHeaderFlagsBothMAndO, // Expect both M and O flags. +}; + +// The expected RA header flags when validating emitted RA message. +ExpectedRaHeaderFlags sExpectedRaHeaderFlags; + // Array containing deprecating prefixes from PIOs in the last processed RA. Array sDeprecatingPrefixes; @@ -371,9 +383,36 @@ void ValidateRouterAdvert(const Icmp6Packet &aPacket) bool sawExpectedPio = false; Array pioPrefixes; Array rioPrefixes; +#if OPENTHREAD_CONFIG_BORDER_ROUTING_STUB_ROUTER_FLAG_IN_EMITTED_RA_ENABLE + bool sawStubRouterFlag = false; +#endif VerifyOrQuit(raMsg.IsValid()); + VerifyOrQuit(raMsg.GetHeader().GetRouterLifetime() == 0); + + switch (sExpectedRaHeaderFlags) + { + case kRaHeaderFlagsSkipChecking: + break; + case kRaHeaderFlagsNone: + VerifyOrQuit(!raMsg.GetHeader().IsManagedAddressConfigFlagSet()); + VerifyOrQuit(!raMsg.GetHeader().IsOtherConfigFlagSet()); + break; + case kRaHeaderFlagsOnlyM: + VerifyOrQuit(raMsg.GetHeader().IsManagedAddressConfigFlagSet()); + VerifyOrQuit(!raMsg.GetHeader().IsOtherConfigFlagSet()); + break; + case kRaHeaderFlagsOnlyO: + VerifyOrQuit(!raMsg.GetHeader().IsManagedAddressConfigFlagSet()); + VerifyOrQuit(raMsg.GetHeader().IsOtherConfigFlagSet()); + break; + case kRaHeaderFlagsBothMAndO: + VerifyOrQuit(raMsg.GetHeader().IsManagedAddressConfigFlagSet()); + VerifyOrQuit(raMsg.GetHeader().IsOtherConfigFlagSet()); + break; + } + sDeprecatingPrefixes.Clear(); for (const Ip6::Nd::Option &option : raMsg) @@ -456,6 +495,7 @@ void ValidateRouterAdvert(const Icmp6Packet &aPacket) VerifyOrQuit(flagsOption.IsValid()); VerifyOrQuit(flagsOption.IsStubRouterFlagSet()); + sawStubRouterFlag = true; break; } #endif @@ -465,6 +505,10 @@ void ValidateRouterAdvert(const Icmp6Packet &aPacket) } } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_STUB_ROUTER_FLAG_IN_EMITTED_RA_ENABLE + VerifyOrQuit(sawStubRouterFlag); +#endif + if (!sRaValidated) { switch (sExpectedPio) @@ -524,6 +568,8 @@ void LogRouterAdvert(const Icmp6Packet &aPacket) VerifyOrQuit(raMsg.IsValid()); + Log(" RA header - M:%u, O:%u", raMsg.GetHeader().IsManagedAddressConfigFlagSet(), + raMsg.GetHeader().IsOtherConfigFlagSet()); Log(" RA header - lifetime %u, prf:%s", raMsg.GetHeader().GetRouterLifetime(), PreferenceToString(raMsg.GetHeader().GetDefaultRouterPreference())); @@ -788,13 +834,28 @@ struct DefaultRoute RoutePreference mPreference; }; +struct RaFlags : public Clearable +{ + RaFlags(void) + : mManagedAddressConfigFlag(false) + , mOtherConfigFlag(false) + , mStubRouterFlag(false) + { + } + + bool mManagedAddressConfigFlag; + bool mOtherConfigFlag; + bool mStubRouterFlag; +}; + template uint16_t BuildRouterAdvert(uint8_t (&aBuffer)[N], const Pio *aPios, uint16_t aNumPios, const Rio *aRios, uint16_t aNumRios, - const DefaultRoute &aDefaultRoute) + const DefaultRoute &aDefaultRoute, + const RaFlags &aRaFlags) { Ip6::Nd::RouterAdvertMessage::Header header; uint16_t length; @@ -802,9 +863,24 @@ uint16_t BuildRouterAdvert(uint8_t (&aBuffer)[N], header.SetRouterLifetime(aDefaultRoute.mLifetime); header.SetDefaultRouterPreference(aDefaultRoute.mPreference); + if (aRaFlags.mManagedAddressConfigFlag) + { + header.SetManagedAddressConfigFlag(); + } + + if (aRaFlags.mOtherConfigFlag) + { + header.SetOtherConfigFlag(); + } + { Ip6::Nd::RouterAdvertMessage raMsg(header, aBuffer); + if (aRaFlags.mStubRouterFlag) + { + SuccessOrQuit(raMsg.AppendFlagsExtensionOption(/* aStubRouterFlag */ true)); + } + for (; aNumPios > 0; aPios++, aNumPios--) { SuccessOrQuit( @@ -827,10 +903,11 @@ void SendRouterAdvert(const Ip6::Address &aRouterAddress, uint16_t aNumPios, const Rio *aRios, uint16_t aNumRios, - const DefaultRoute &aDefaultRoute) + const DefaultRoute &aDefaultRoute, + const RaFlags &aRaFlags) { uint8_t buffer[kMaxRaSize]; - uint16_t length = BuildRouterAdvert(buffer, aPios, aNumPios, aRios, aNumRios, aDefaultRoute); + uint16_t length = BuildRouterAdvert(buffer, aPios, aNumPios, aRios, aNumRios, aDefaultRoute, aRaFlags); SendRouterAdvert(aRouterAddress, buffer, length); Log("Sending RA from router %s", aRouterAddress.ToString().AsCString()); @@ -841,37 +918,48 @@ template void SendRouterAdvert(const Ip6::Address &aRouterAddress, const Pio (&aPios)[kNumPios], const Rio (&aRios)[kNumRios], - const DefaultRoute &aDefaultRoute = DefaultRoute(0, NetworkData::kRoutePreferenceMedium)) + const DefaultRoute &aDefaultRoute = DefaultRoute(0, NetworkData::kRoutePreferenceMedium), + const RaFlags &aRaFlags = RaFlags()) { - SendRouterAdvert(aRouterAddress, aPios, kNumPios, aRios, kNumRios, aDefaultRoute); + SendRouterAdvert(aRouterAddress, aPios, kNumPios, aRios, kNumRios, aDefaultRoute, aRaFlags); } template void SendRouterAdvert(const Ip6::Address &aRouterAddress, const Pio (&aPios)[kNumPios], - const DefaultRoute &aDefaultRoute = DefaultRoute(0, NetworkData::kRoutePreferenceMedium)) + const DefaultRoute &aDefaultRoute = DefaultRoute(0, NetworkData::kRoutePreferenceMedium), + const RaFlags &aRaFlags = RaFlags()) { - SendRouterAdvert(aRouterAddress, aPios, kNumPios, nullptr, 0, aDefaultRoute); + SendRouterAdvert(aRouterAddress, aPios, kNumPios, nullptr, 0, aDefaultRoute, aRaFlags); } template void SendRouterAdvert(const Ip6::Address &aRouterAddress, const Rio (&aRios)[kNumRios], - const DefaultRoute &aDefaultRoute = DefaultRoute(0, NetworkData::kRoutePreferenceMedium)) + const DefaultRoute &aDefaultRoute = DefaultRoute(0, NetworkData::kRoutePreferenceMedium), + const RaFlags &aRaFlags = RaFlags()) { - SendRouterAdvert(aRouterAddress, nullptr, 0, aRios, kNumRios, aDefaultRoute); + SendRouterAdvert(aRouterAddress, nullptr, 0, aRios, kNumRios, aDefaultRoute, aRaFlags); } -void SendRouterAdvert(const Ip6::Address &aRouterAddress, const DefaultRoute &aDefaultRoute) +void SendRouterAdvert(const Ip6::Address &aRouterAddress, + const DefaultRoute &aDefaultRoute, + const RaFlags &aRaFlags = RaFlags()) { - SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, aDefaultRoute); + SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, aDefaultRoute, aRaFlags); +} + +void SendRouterAdvert(const Ip6::Address &aRouterAddress, const RaFlags &aRaFlags) +{ + SendRouterAdvert(aRouterAddress, nullptr, 0, nullptr, 0, DefaultRoute(0, NetworkData::kRoutePreferenceMedium), + aRaFlags); } template void SendRouterAdvertToBorderRoutingProcessIcmp6Ra(const Pio (&aPios)[kNumPios]) { uint8_t buffer[kMaxRaSize]; - uint16_t length = - BuildRouterAdvert(buffer, aPios, kNumPios, nullptr, 0, DefaultRoute(0, NetworkData::kRoutePreferenceMedium)); + uint16_t length = BuildRouterAdvert(buffer, aPios, kNumPios, nullptr, 0, + DefaultRoute(0, NetworkData::kRoutePreferenceMedium), RaFlags()); otPlatBorderRoutingProcessIcmp6Ra(sInstance, buffer, length); Log("Passing RA to otPlatBorderRoutingProcessIcmp6Ra"); @@ -996,6 +1084,66 @@ void VerifyPrefixTable(const OnLinkPrefix *aOnLinkPrefixes, void VerifyPrefixTableIsEmpty(void) { VerifyPrefixTable(nullptr, 0, nullptr, 0); } +struct InfraRouter +{ + InfraRouter(const Ip6::Address &aAddress, + bool aManagedAddressConfigFlag, + bool aOtherConfigFlag, + bool aStubRouterFlag) + : mAddress(aAddress) + { + mFlags.Clear(); + mFlags.mManagedAddressConfigFlag = aManagedAddressConfigFlag; + mFlags.mOtherConfigFlag = aOtherConfigFlag; + mFlags.mStubRouterFlag = aStubRouterFlag; + } + + Ip6::Address mAddress; + RaFlags mFlags; +}; + +template void VerifyDiscoveredRouters(const InfraRouter (&aRouters)[kNumRouters]) +{ + VerifyDiscoveredRouters(aRouters, kNumRouters); +} + +void VerifyDiscoveredRouters(const InfraRouter *aRouters, uint16_t aNumRouters) +{ + BorderRouter::RoutingManager::PrefixTableIterator iter; + BorderRouter::RoutingManager::RouterEntry entry; + uint16_t count = 0; + + Log("VerifyDiscoveredRouters()"); + + sInstance->Get().InitPrefixTableIterator(iter); + + while (sInstance->Get().GetNextRouterEntry(iter, entry) == kErrorNone) + { + bool didFind = false; + + Log(" address:%s, M:%u, O:%u, StubRouter:%u", AsCoreType(&entry.mAddress).ToString().AsCString(), + entry.mManagedAddressConfigFlag, entry.mOtherConfigFlag, entry.mStubRouterFlag); + + for (uint16_t index = 0; index < aNumRouters; index++) + { + if (AsCoreType(&entry.mAddress) == aRouters[index].mAddress) + { + VerifyOrQuit(entry.mManagedAddressConfigFlag == aRouters[index].mFlags.mManagedAddressConfigFlag); + VerifyOrQuit(entry.mOtherConfigFlag == aRouters[index].mFlags.mOtherConfigFlag); + VerifyOrQuit(entry.mStubRouterFlag == aRouters[index].mFlags.mStubRouterFlag); + didFind = true; + } + } + + VerifyOrQuit(didFind); + count++; + } + + VerifyOrQuit(count == aNumRouters); +} + +void VerifyDiscoveredRoutersIsEmpty(void) { VerifyDiscoveredRouters(nullptr, 0); } + void InitTest(bool aEnablBorderRouting = false, bool aAfterReset = false) { uint32_t delay = 10000; @@ -1032,19 +1180,20 @@ void InitTest(bool aEnablBorderRouting = false, bool aAfterReset = false) SuccessOrQuit(otThreadSetEnabled(sInstance, true)); SuccessOrQuit(otBorderRoutingSetEnabled(sInstance, aEnablBorderRouting)); + // Reset all test flags + sRsEmitted = false; + sRaValidated = false; + sExpectedPio = kNoPio; + sExpectedRios.Clear(); + sRespondToNs = true; + sExpectedRaHeaderFlags = kRaHeaderFlagsNone; + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Ensure device starts as leader. AdvanceTime(delay); VerifyOrQuit(otThreadGetDeviceRole(sInstance) == OT_DEVICE_ROLE_LEADER); - - // Reset all test flags - sRsEmitted = false; - sRaValidated = false; - sExpectedPio = kNoPio; - sExpectedRios.Clear(); - sRespondToNs = true; } void FinalizeTest(void) @@ -2599,6 +2748,178 @@ void TestRouterNsProbe(void) FinalizeTest(); } +void TestLearningAndCopyingOfFlags(void) +{ + Ip6::Prefix localOnLink; + Ip6::Prefix localOmr; + Ip6::Prefix onLinkPrefix = PrefixFromString("2000:abba:baba::", 64); + Ip6::Address routerAddressA = AddressFromString("fd00::aaaa"); + Ip6::Address routerAddressB = AddressFromString("fd00::bbbb"); + Ip6::Address routerAddressC = AddressFromString("fd00::cccc"); + uint16_t heapAllocations; + RaFlags raFlags; + + Log("--------------------------------------------------------------------------------------------"); + Log("TestLearningAndCopyingOfFlags"); + + InitTest(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Start Routing Manager. Check emitted RS and RA messages. + + sRsEmitted = false; + sRaValidated = false; + sExpectedPio = kPioAdvertisingLocalOnLink; + sExpectedRios.Clear(); + + heapAllocations = sHeapAllocatedPtrs.GetLength(); + SuccessOrQuit(sInstance->Get().SetEnabled(true)); + + SuccessOrQuit(sInstance->Get().GetOnLinkPrefix(localOnLink)); + SuccessOrQuit(sInstance->Get().GetOmrPrefix(localOmr)); + + Log("Local on-link prefix is %s", localOnLink.ToString().AsCString()); + Log("Local OMR prefix is %s", localOmr.ToString().AsCString()); + + sExpectedRios.Add(localOmr); + + AdvanceTime(30000); + + VerifyOrQuit(sRsEmitted); + VerifyOrQuit(sRaValidated); + VerifyOrQuit(sExpectedRios.SawAll()); + Log("Received RA was validated"); + + VerifyDiscoveredRoutersIsEmpty(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send an RA from router A with M flag set, and make sure the + // emitted RA from BR also includes M flag. + + raFlags.Clear(); + raFlags.mManagedAddressConfigFlag = true; + + SendRouterAdvert(routerAddressA, raFlags); + + AdvanceTime(1); + VerifyDiscoveredRouters({InfraRouter(routerAddressA, /* M */ true, /* O */ false, /* StubRouter */ false)}); + + sRaValidated = false; + sExpectedRaHeaderFlags = kRaHeaderFlagsOnlyM; + + AdvanceTime(610 * 1000); + VerifyOrQuit(sRaValidated); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send an RA from router A without the M flag. Now the emitted + // RA should no longer contain the M flag. + + raFlags.Clear(); + + SendRouterAdvert(routerAddressA, raFlags); + + sRaValidated = false; + sExpectedRaHeaderFlags = kRaHeaderFlagsNone; + + AdvanceTime(1); + VerifyDiscoveredRoutersIsEmpty(); + + AdvanceTime(610 * 1000); + VerifyOrQuit(sRaValidated); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send an RA from router A with both M and StubRouter flags. + // Since it is from a stub router, the M flag should be ignored. + // Ensure emitted RA does not set the M flag. + + raFlags.Clear(); + raFlags.mManagedAddressConfigFlag = true; + raFlags.mStubRouterFlag = true; + + SendRouterAdvert(routerAddressA, raFlags); + + AdvanceTime(1); + VerifyDiscoveredRouters({InfraRouter(routerAddressA, /* M */ true, /* O */ false, /* StubRouter */ true)}); + + sRaValidated = false; + sExpectedRaHeaderFlags = kRaHeaderFlagsNone; + + AdvanceTime(610 * 1000); + VerifyOrQuit(sRaValidated); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send an RA from router B with O flag and check that emitted + // RA include the same flag. + + raFlags.Clear(); + raFlags.mOtherConfigFlag = true; + + SendRouterAdvert(routerAddressB, raFlags); + + AdvanceTime(1); + VerifyDiscoveredRouters({InfraRouter(routerAddressA, /* M */ true, /* O */ false, /* StubRouter */ true), + InfraRouter(routerAddressB, /* M */ false, /* O */ true, /* StubRouter */ false)}); + + sRaValidated = false; + sExpectedRaHeaderFlags = kRaHeaderFlagsOnlyO; + + AdvanceTime(610 * 1000); + VerifyOrQuit(sRaValidated); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Send an RA from router C with M flag and check that emitted + // RA now includes both M and O flags. + + raFlags.Clear(); + raFlags.mManagedAddressConfigFlag = true; + + SendRouterAdvert(routerAddressC, {Pio(onLinkPrefix, kValidLitime, kPreferredLifetime)}, + DefaultRoute(0, NetworkData::kRoutePreferenceMedium), raFlags); + + AdvanceTime(1); + VerifyDiscoveredRouters({InfraRouter(routerAddressA, /* M */ true, /* O */ false, /* StubRouter */ true), + InfraRouter(routerAddressB, /* M */ false, /* O */ true, /* StubRouter */ false), + InfraRouter(routerAddressC, /* M */ true, /* O */ false, /* StubRouter */ false)}); + + sRaValidated = false; + sExpectedPio = kPioDeprecatingLocalOnLink; + sExpectedRaHeaderFlags = kRaHeaderFlagsBothMAndO; + + AdvanceTime(610 * 1000); + VerifyOrQuit(sRaValidated); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Stop responding to NS, this should cause all routers + // to age and considered offline + + sRespondToNs = false; + + sExpectedRaHeaderFlags = kRaHeaderFlagsSkipChecking; + + AdvanceTime(300 * 1000); + + // Router C should be in the table since it will have a deprecating + // on-link prefix. + VerifyDiscoveredRouters({InfraRouter(routerAddressC, /* M */ true, /* O */ false, /* StubRouter */ false)}); + + sRaValidated = false; + sExpectedPio = kPioAdvertisingLocalOnLink; + sExpectedRaHeaderFlags = kRaHeaderFlagsNone; + + AdvanceTime(610 * 1000); + VerifyOrQuit(sRaValidated); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + SuccessOrQuit(sInstance->Get().SetEnabled(false)); + VerifyDiscoveredRoutersIsEmpty(); + + VerifyOrQuit(heapAllocations == sHeapAllocatedPtrs.GetLength()); + + Log("End of TestLearningAndCopyingOfFlags"); + FinalizeTest(); +} + void TestConflictingPrefix(void) { static const otExtendedPanId kExtPanId1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x6, 0x7, 0x08}}; @@ -3569,6 +3890,7 @@ int main(void) ot::TestExtPanIdChange(); ot::TestConflictingPrefix(); ot::TestRouterNsProbe(); + ot::TestLearningAndCopyingOfFlags(); #if OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE ot::TestSavedOnLinkPrefixes(); #endif @@ -3580,7 +3902,7 @@ int main(void) #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE ot::TestBorderRoutingProcessPlatfromGeneratedNd(); -#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE +#endif printf("All tests passed\n"); #else