diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp index c11e6d090..094e5ef82 100644 --- a/src/core/border_router/routing_manager.cpp +++ b/src/core/border_router/routing_manager.cpp @@ -865,7 +865,8 @@ void RoutingManager::OmrPrefixManager::UpdateLocalPrefix(void) { case kOmrConfigAuto: #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE - if (Get().mPdPrefixManager.HasPrefix()) + if (Get().mPdPrefixManager.HasPrefix() && + !Get().mPdPrefixManager.HasConflictWithOnLinkPrefixes()) { if (mLocalPrefix.GetPrefix() != Get().mPdPrefixManager.GetPrefix()) { @@ -877,6 +878,7 @@ void RoutingManager::OmrPrefixManager::UpdateLocalPrefix(void) } else #endif + if (mLocalPrefix.GetPrefix() != mGeneratedPrefix) { RemoveLocalFromNetData(); @@ -2504,6 +2506,7 @@ void RoutingManager::TxRaInfo::CalculateHash(const RouterAdvert::RxMessage &aRaM RoutingManager::PdPrefixManager::PdPrefixManager(Instance &aInstance) : InstanceLocator(aInstance) , mState(kDhcp6PdStateDisabled) + , mConflicted(false) , mNumPlatformPioProcessed(0) , mNumPlatformRaReceived(0) , mLastPlatformRaTime(0) @@ -2636,6 +2639,8 @@ void RoutingManager::PdPrefixManager::WithdrawPrefix(void) LogInfo("Withdrew DHCPv6 PD prefix %s", mPrefix.GetPrefix().ToString().AsCString()); mPrefix.Clear(); + mConflicted = false; + mTimer.Stop(); Get().ScheduleRoutingPolicyEvaluation(kImmediately); @@ -2727,7 +2732,10 @@ void RoutingManager::PdPrefixManager::ApplyFavoredPrefix(const PdPrefix &aFavore if (aFavoredPrefix.IsFavoredOver(mPrefix)) { mPrefix = aFavoredPrefix; + LogInfo("DHCPv6 PD prefix set to %s", mPrefix.GetPrefix().ToString().AsCString()); + CheckConflictWithOnLinkPrefixes(); + Get().ScheduleRoutingPolicyEvaluation(kImmediately); #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE @@ -2783,6 +2791,35 @@ exit: return; } +void RoutingManager::PdPrefixManager::CheckConflictWithOnLinkPrefixes(void) +{ + // Checks if the delegated PD prefix is also seen as an on-link + // prefix. This protects against DHCPv6-PD server misbehavior + // assigning the same prefix to multiple requesters. + // + // If a conflict is detected, the delegated PD prefix is no longer + // used as OMR prefix, reverting back to using the local OMR + // prefix. Once the conflict is resolved, the PD prefix can be + // used as OMR prefix again. + + bool conflicted; + + VerifyOrExit(HasPrefix()); + + conflicted = Get().IsPrefixOnLink(mPrefix.GetPrefix()); + VerifyOrExit(conflicted != mConflicted); + + mConflicted = conflicted; + + LogInfo("DHCPv6 PD prefix %s %sconflicts with the advertised on-link prefixes", + mPrefix.GetPrefix().ToString().AsCString(), mConflicted ? "" : "no longer "); + + Get().ScheduleRoutingPolicyEvaluation(kImmediately); + +exit: + return; +} + #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE void RoutingManager::PdPrefixManager::HandleRecordHistoryTask(void) diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp index 85bc446d7..e6a1a66d0 100644 --- a/src/core/border_router/routing_manager.hpp +++ b/src/core/border_router/routing_manager.hpp @@ -961,8 +961,10 @@ private: void Start(void) { Evaluate(); } void Stop(void) { Evaluate(); } bool HasPrefix(void) const { return !mPrefix.IsEmpty(); } + bool HasConflictWithOnLinkPrefixes(void) const { return mConflicted; } const Ip6::Prefix &GetPrefix(void) const { return mPrefix.GetPrefix(); } State GetState(void) const { return mState; } + void CheckConflictWithOnLinkPrefixes(void); void ProcessPrefixesFromRa(const InfraIf::Icmp6Packet &aRaPacket); void ProcessPrefix(const Dhcp6PdPrefix &aPrefix); @@ -1000,6 +1002,7 @@ private: #endif State mState; + bool mConflicted; uint32_t mNumPlatformPioProcessed; uint32_t mNumPlatformRaReceived; TimeMilli mLastPlatformRaTime; diff --git a/src/core/border_router/rx_ra_tracker.cpp b/src/core/border_router/rx_ra_tracker.cpp index 036e02c4c..dd0291ffe 100644 --- a/src/core/border_router/rx_ra_tracker.cpp +++ b/src/core/border_router/rx_ra_tracker.cpp @@ -856,6 +856,16 @@ void RxRaTracker::Evaluate(void) mEventTask.Post(); } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Check for possible conflict between delegated DHCPv6-PD prefix + // and any of the observed on-link prefixes. This protects against + // DHCPv6-PD server misbehavior (assigning same prefix to multiple + // requesters). + + Get().mPdPrefixManager.CheckConflictWithOnLinkPrefixes(); +#endif + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Schedule timers @@ -1176,6 +1186,26 @@ exit: return isOnLink; } +bool RxRaTracker::IsPrefixOnLink(const Ip6::Prefix &aPrefix) const +{ + bool isOnLink = false; + + for (const Router &router : mRouters) + { + for (const OnLinkPrefix &onLinkPrefix : router.mOnLinkPrefixes) + { + if (aPrefix == onLinkPrefix.GetPrefix()) + { + isOnLink = true; + ExitNow(); + } + } + } + +exit: + return isOnLink; +} + bool RxRaTracker::IsAddressReachableThroughExplicitRoute(const Ip6::Address &aAddress) const { // Checks whether the `aAddress` matches any discovered route diff --git a/src/core/border_router/rx_ra_tracker.hpp b/src/core/border_router/rx_ra_tracker.hpp index 350780c2b..85b049e9d 100644 --- a/src/core/border_router/rx_ra_tracker.hpp +++ b/src/core/border_router/rx_ra_tracker.hpp @@ -307,6 +307,16 @@ public: */ bool IsAddressReachableThroughExplicitRoute(const Ip6::Address &aAddress) const; + /** + * Indicates whether a given prefix is seen as an on-link prefix in any tracked RA. + * + * @param[in] aPrefix The IPv6 prefix to check. + * + * @retval TRUE The prefix is on-link. + * @retval FALSE The prefix is not on-link. + */ + bool IsPrefixOnLink(const Ip6::Prefix &aPrefix) const; + // Callbacks notifying of changes void HandleLocalOnLinkPrefixChanged(void); diff --git a/tests/unit/test_routing_manager.cpp b/tests/unit/test_routing_manager.cpp index a30d56461..adec1ecd9 100644 --- a/tests/unit/test_routing_manager.cpp +++ b/tests/unit/test_routing_manager.cpp @@ -5101,6 +5101,126 @@ void TestDhcp6Pd(void) FinalizeTest(); } +void TestDhcp6PdConflict(void) +{ + Ip6::Prefix localOmr; + Ip6::Prefix pdPrefix = PrefixFromString("2001:db8:dead:beef::", 64); + Ip6::Address routerAddressA = AddressFromString("fd00::aaaa"); + uint16_t heapAllocations; + + Log("--------------------------------------------------------------------------------------------"); + Log("TestDhcp6PdConflict"); + + InitTest(/* aEnableBorderRouting */ true); + heapAllocations = sHeapAllocatedPtrs.GetLength(); + + sInstance->Get().SetDhcp6PdEnabled(true); + SuccessOrQuit(sInstance->Get().GetOmrPrefix(localOmr)); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Report a PD prefix and check that its used as OMR prefix + + Log("Report DHCPv6-PD prefix"); + ReportPdPrefixesAsRa({Pio(pdPrefix, kValidLitime, kPreferredLifetime)}); + + sExpectedRios.Add(pdPrefix); + AdvanceTime(10 * 1000); + + VerifyPdOmrPrefix(pdPrefix); + VerifyOrQuit(sExpectedRios.SawAll()); + VerifyOmrPrefixInNetData(pdPrefix, /* aDefaultRoute */ false); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Advertise the same prefix as on-link from a router. + + Log("Router A advertises PD prefix as on-link"); + SendRouterAdvert(routerAddressA, {Pio(pdPrefix, 200, 200)}); + + // Check that the PD prefix is no longer used as OMR prefix due to + // conflict. The OMR should switch back to local OMR. + + sExpectedRios.Clear(); + sExpectedRios.Add(localOmr); + + AdvanceTime(10 * 1000); + VerifyOrQuit(sExpectedRios.SawAll()); + + VerifyOmrPrefixInNetData(localOmr, /* aDefaultRoute */ true); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Wait for the PIO from Router A to expire. We renew the PD + // prefix during this time to ensure it stays valid. + + Log("Wait for Router A PIO to expire"); + + AdvanceTime(300 * 1000); + ReportPdPrefixesAsRa({Pio(pdPrefix, kValidLitime, kPreferredLifetime)}); + AdvanceTime(300 * 1000); + + // Router A entry should be expired and removed. The PD prefix is still + // valid (renewed). The conflict should be resolved. Validate that PD + // prefix is again being use as OMR prefix. + + sExpectedRios.Clear(); + sExpectedRios.Add(pdPrefix); + + AdvanceTime(100 * 1000); + + VerifyPdOmrPrefix(pdPrefix); + VerifyOrQuit(sExpectedRios.SawAll()); + VerifyOmrPrefixInNetData(pdPrefix, /* aDefaultRoute */ false); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Remove the PD prefix + + ReportPdPrefixesAsRa({Pio(pdPrefix, 0, 0)}); + + AdvanceTime(1 * 1000); + VerifyNoPdOmrPrefix(); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Now Advertise the PD prefix as on-link from a router first. + + Log("Router A advertises PD prefix as on-link before delegating the prefix"); + SendRouterAdvert(routerAddressA, {Pio(pdPrefix, 200, 200)}); + + // Check that local OMR is used. + + sExpectedRios.Clear(); + sExpectedRios.Add(localOmr); + + AdvanceTime(10 * 1000); + VerifyOrQuit(sExpectedRios.SawAll()); + + VerifyOmrPrefixInNetData(localOmr, /* aDefaultRoute */ true); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Report the same PD prefix. Validate that local OMR prefix is + // still being used. + + Log("Delegate PD prefix which conflicts with already advertised on-link prefix from router A"); + ReportPdPrefixesAsRa({Pio(pdPrefix, kValidLitime, kPreferredLifetime)}); + + sExpectedRios.Clear(); + sExpectedRios.Add(localOmr); + + AdvanceTime(100 * 1000); + VerifyOrQuit(sExpectedRios.SawAll()); + + VerifyOmrPrefixInNetData(localOmr, /* aDefaultRoute */ true); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + SuccessOrQuit(sInstance->Get().SetEnabled(false)); + AdvanceTime(3000); + + VerifyOrQuit(sHeapAllocatedPtrs.GetLength() <= heapAllocations); + + Log("End of TestDhcp6PdConflict"); + + FinalizeTest(); +} + #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE static void HandleRdnssChanged(void *aContext) @@ -5378,6 +5498,7 @@ int main(void) #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE ot::TestDhcp6Pd(); + ot::TestDhcp6PdConflict(); #endif ot::TestRdnss();