diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index 2e92b7160..b4c1b0ae5 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -609,11 +609,11 @@ void RoutingManager::SendRouterAdvertisement(RouterAdvTxMode aRaTxMode) VerifyOrExit(raMsg.ContainsAnyOptions()); - ++mRaInfo.mTxCount; - destAddress.SetToLinkLocalAllNodesMulticast(); raMsg.GetAsPacket(packet); + mRaInfo.IncrementTxCountAndSaveHash(packet); + SuccessOrExit(error = mInfraIf.Send(packet, destAddress)); mRaInfo.mLastTxTime = TimerMilli::GetNow(); @@ -629,73 +629,6 @@ exit: } } -bool RoutingManager::IsReceivedRouterAdvertFromManager(const RouterAdvert::RxMessage &aRaMessage) const -{ - // Determines whether or not a received RA message was prepared by - // by `RoutingManager` itself. - - bool isFromManager = false; - uint16_t rioCount = 0; - Ip6::Prefix prefix; - - VerifyOrExit(aRaMessage.ContainsAnyOptions()); - - for (const Option &option : aRaMessage) - { - switch (option.GetType()) - { - case Option::kTypePrefixInfo: - { - const PrefixInfoOption &pio = static_cast(option); - - VerifyOrExit(pio.IsValid()); - pio.GetPrefix(prefix); - - // If it is a non-deprecated PIO, it should match the - // local on-link prefix. - - if (pio.GetPreferredLifetime() > 0) - { - VerifyOrExit(prefix == mOnLinkPrefixManager.GetLocalPrefix()); - } - - break; - } - - case Option::kTypeRouteInfo: - { - // RIO (with non-zero lifetime) should match entries from - // `mRioAdvertiser`. We keep track of the number of matched - // RIOs and check after the loop ends that all entries were - // seen. - - const RouteInfoOption &rio = static_cast(option); - - VerifyOrExit(rio.IsValid()); - rio.GetPrefix(prefix); - - if (rio.GetRouteLifetime() != 0) - { - VerifyOrExit(mRioAdvertiser.HasAdvertised(prefix)); - rioCount++; - } - - break; - } - - default: - ExitNow(); - } - } - - VerifyOrExit(rioCount == mRioAdvertiser.GetAdvertisedRioCount()); - - isFromManager = true; - -exit: - return isFromManager; -} - bool RoutingManager::IsValidBrUlaPrefix(const Ip6::Prefix &aBrUlaPrefix) { return aBrUlaPrefix.mLength == kBrUlaPrefixLength && aBrUlaPrefix.mPrefix.mFields.m8[0] == 0xfd; @@ -941,7 +874,7 @@ void RoutingManager::UpdateRouterAdvertHeader(const RouterAdvert::RxMessage *aRo // We skip and do not update RA header if the received RA message // was not prepared and sent by `RoutingManager` itself. - VerifyOrExit(!IsReceivedRouterAdvertFromManager(*aRouterAdvertMessage)); + VerifyOrExit(!mRaInfo.IsRaFromManager(*aRouterAdvertMessage)); } oldHeader = mRaInfo.mHeader; @@ -3451,6 +3384,66 @@ exit: #endif // OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE +//--------------------------------------------------------------------------------------------------------------------- +// RaInfo + +void RoutingManager::RaInfo::IncrementTxCountAndSaveHash(const InfraIf::Icmp6Packet &aRaMessage) +{ + mTxCount++; + mLastHashIndex++; + + if (mLastHashIndex == kNumHashEntries) + { + mLastHashIndex = 0; + } + + CalculateHash(aRaMessage, mHashes[mLastHashIndex]); +} + +bool RoutingManager::RaInfo::IsRaFromManager(const Ip6::Nd::RouterAdvert::RxMessage &aRaMessage) const +{ + // Determines whether or not a received RA message was prepared by + // by `RoutingManager` itself (is present in the saved `mHashes`). + + bool isFromManager = false; + uint16_t hashIndex = mLastHashIndex; + uint32_t count = Min(mTxCount, kNumHashEntries); + Hash hash; + + CalculateHash(aRaMessage.GetAsPacket(), hash); + + for (; count > 0; count--) + { + if (mHashes[hashIndex] == hash) + { + isFromManager = true; + break; + } + + // Go to the previous index (ring buffer) + + if (hashIndex == 0) + { + hashIndex = kNumHashEntries - 1; + } + else + { + hashIndex--; + } + } + + return isFromManager; +} + +void RoutingManager::RaInfo::CalculateHash(const InfraIf::Icmp6Packet &aRaMessage, Hash &aHash) +{ + Crypto::Sha256 sha256; + + sha256.Start(); + sha256.Update(aRaMessage.GetBytes(), aRaMessage.GetLength()); + sha256.Finish(aHash); +} + //--------------------------------------------------------------------------------------------------------------------- // RsSender diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index 4c424e407..bb376eedc 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -62,6 +62,7 @@ #include "common/pool.hpp" #include "common/string.hpp" #include "common/timer.hpp" +#include "crypto/sha256.hpp" #include "net/ip6.hpp" #include "net/nat64_translator.hpp" #include "net/nd6.hpp" @@ -1160,26 +1161,44 @@ private: struct RaInfo { - // Tracks info about emitted RA messages: Number of RAs sent, - // last tx time, header to use and whether the header is - // discovered from receiving RAs from the host itself. This - // ensures that if an entity on host is advertising certain + // Tracks info about emitted RA messages: + // + // - Number of RAs sent + // - Last RA TX time + // - Hashes of last TX RAs (to tell if a received RA is from + // `RoutingManager` itself) + // - RA header to use, and + // - Whether the RA header is discovered from receiving RAs + // from the host itself. + // + // This ensures that if an entity on host is advertising certain // info in its RA header (e.g., a default route), the RAs we // emit from `RoutingManager` also include the same header. + typedef Crypto::Sha256::Hash Hash; + + static constexpr uint16_t kNumHashEntries = 5; + RaInfo(void) : mHeaderUpdateTime(TimerMilli::GetNow()) , mIsHeaderFromHost(false) , mTxCount(0) , mLastTxTime(TimerMilli::GetNow() - kMinDelayBetweenRtrAdvs) + , mLastHashIndex(0) { } + void IncrementTxCountAndSaveHash(const InfraIf::Icmp6Packet &aRaMessage); + bool IsRaFromManager(const Ip6::Nd::RouterAdvert::RxMessage &aRaMessage) const; + static void CalculateHash(const InfraIf::Icmp6Packet &aRaMessage, Hash &aHash); + RouterAdvert::Header mHeader; TimeMilli mHeaderUpdateTime; bool mIsHeaderFromHost; uint32_t mTxCount; TimeMilli mLastTxTime; + Hash mHashes[kNumHashEntries]; + uint16_t mLastHashIndex; }; void HandleRsSenderTimer(void) { mRsSender.HandleTimer(); } @@ -1296,7 +1315,6 @@ private: bool NetworkDataContainsOmrPrefix(const Ip6::Prefix &aPrefix) const; bool NetworkDataContainsUlaRoute(void) const; void UpdateRouterAdvertHeader(const RouterAdvert::RxMessage *aRouterAdvertMessage); - bool IsReceivedRouterAdvertFromManager(const RouterAdvert::RxMessage &aRaMessage) const; void ResetDiscoveredPrefixStaleTimer(void); static bool IsValidBrUlaPrefix(const Ip6::Prefix &aBrUlaPrefix); diff --git a/tests/unit/test_routing_manager.cpp b/tests/unit/test_routing_manager.cpp index dde7c4bc7..a474de4b1 100644 --- a/tests/unit/test_routing_manager.cpp +++ b/tests/unit/test_routing_manager.cpp @@ -160,6 +160,12 @@ 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. +// Indicate whether or not to check the emitted RA header (default route) lifetime +bool sCheckRaHeaderLifetime; + +// Expected default route lifetime in emitted RA header by BR. +uint32_t sExpectedRaHeaderLifetime; + enum ExpectedRaHeaderFlags { kRaHeaderFlagsSkipChecking, // Skip checking the RA header flags. @@ -424,7 +430,10 @@ void ValidateRouterAdvert(const Icmp6Packet &aPacket) VerifyOrQuit(raMsg.IsValid()); - VerifyOrQuit(raMsg.GetHeader().GetRouterLifetime() == 0); + if (sCheckRaHeaderLifetime) + { + VerifyOrQuit(raMsg.GetHeader().GetRouterLifetime() == sExpectedRaHeaderLifetime); + } switch (sExpectedRaHeaderFlags) { @@ -1188,8 +1197,10 @@ void InitTest(bool aEnablBorderRouting = false, bool aAfterReset = false) sRaValidated = false; sExpectedPio = kNoPio; sExpectedRios.Clear(); - sRespondToNs = true; - sExpectedRaHeaderFlags = kRaHeaderFlagsNone; + sRespondToNs = true; + sExpectedRaHeaderFlags = kRaHeaderFlagsNone; + sCheckRaHeaderLifetime = true; + sExpectedRaHeaderLifetime = 0; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Ensure device starts as leader. @@ -2950,6 +2961,93 @@ void TestLearningAndCopyingOfFlags(void) FinalizeTest(); } +void TestLearnRaHeader(void) +{ + Ip6::Prefix localOnLink; + Ip6::Prefix localOmr; + Ip6::Prefix onLinkPrefix = PrefixFromString("2000:abba:baba::", 64); + uint16_t heapAllocations; + + Log("--------------------------------------------------------------------------------------------"); + Log("TestLearnRaHeader"); + + 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 the same address (another entity on the device) + // advertising a default route. + + SendRouterAdvert(sInfraIfAddress, DefaultRoute(1000, NetworkData::kRoutePreferenceLow)); + + AdvanceTime(1); + VerifyDiscoveredRouters({InfraRouter(sInfraIfAddress, /* M */ false, /* O */ false, /* StubRouter */ false)}); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // RoutingManager should learn the header from the + // received RA (from same address) and start advertising + // the same default route lifetime in the emitted RAs. + + sRaValidated = false; + sCheckRaHeaderLifetime = true; + sExpectedRaHeaderLifetime = 1000; + + AdvanceTime(30 * 1000); + VerifyOrQuit(sRaValidated); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Wait for longer than entry lifetime (for it to expire) and + // make sure `RoutingManager` stops advertising default route. + + sCheckRaHeaderLifetime = false; + + AdvanceTime(1000 * 1000); + + sRaValidated = false; + sCheckRaHeaderLifetime = true; + sExpectedRaHeaderLifetime = 0; + + AdvanceTime(700 * 1000); + VerifyOrQuit(sRaValidated); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + SuccessOrQuit(sInstance->Get().SetEnabled(false)); + VerifyDiscoveredRoutersIsEmpty(); + + VerifyOrQuit(heapAllocations == sHeapAllocatedPtrs.GetLength()); + + Log("End of TestLearnRaHeader"); + FinalizeTest(); +} + void TestConflictingPrefix(void) { static const otExtendedPanId kExtPanId1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x6, 0x7, 0x08}}; @@ -3921,6 +4019,7 @@ int main(void) ot::TestConflictingPrefix(); ot::TestRouterNsProbe(); ot::TestLearningAndCopyingOfFlags(); + ot::TestLearnRaHeader(); #if OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE ot::TestSavedOnLinkPrefixes(); #endif