[border-router] detect DHCPv6-PD prefix conflict with on-link prefixes (#12346)

This commit updates `RoutingManager` to detect if a delegated DHCPv6
PD prefix conflicts with any on-link prefix advertised on the
infrastructure link.

This protects against potential DHCPv6 server misbehavior and bugs
where the same prefix might be assigned to multiple requesters.

If a conflict is detected, the delegated PD prefix is marked as
conflicted and is no longer used as the OMR prefix. Instead, we
revert to using the locally generated OMR prefix. If the conflict
is resolved, the delegated PD prefix is used again.

A new unit test `TestDhcp6PdConflict()` is added to verify this
behavior.
This commit is contained in:
Abtin Keshavarzian
2026-02-03 07:53:22 -08:00
committed by GitHub
parent 54b936367d
commit 7c87684f1b
5 changed files with 202 additions and 1 deletions
+38 -1
View File
@@ -865,7 +865,8 @@ void RoutingManager::OmrPrefixManager::UpdateLocalPrefix(void)
{ {
case kOmrConfigAuto: case kOmrConfigAuto:
#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
if (Get<RoutingManager>().mPdPrefixManager.HasPrefix()) if (Get<RoutingManager>().mPdPrefixManager.HasPrefix() &&
!Get<RoutingManager>().mPdPrefixManager.HasConflictWithOnLinkPrefixes())
{ {
if (mLocalPrefix.GetPrefix() != Get<RoutingManager>().mPdPrefixManager.GetPrefix()) if (mLocalPrefix.GetPrefix() != Get<RoutingManager>().mPdPrefixManager.GetPrefix())
{ {
@@ -877,6 +878,7 @@ void RoutingManager::OmrPrefixManager::UpdateLocalPrefix(void)
} }
else else
#endif #endif
if (mLocalPrefix.GetPrefix() != mGeneratedPrefix) if (mLocalPrefix.GetPrefix() != mGeneratedPrefix)
{ {
RemoveLocalFromNetData(); RemoveLocalFromNetData();
@@ -2504,6 +2506,7 @@ void RoutingManager::TxRaInfo::CalculateHash(const RouterAdvert::RxMessage &aRaM
RoutingManager::PdPrefixManager::PdPrefixManager(Instance &aInstance) RoutingManager::PdPrefixManager::PdPrefixManager(Instance &aInstance)
: InstanceLocator(aInstance) : InstanceLocator(aInstance)
, mState(kDhcp6PdStateDisabled) , mState(kDhcp6PdStateDisabled)
, mConflicted(false)
, mNumPlatformPioProcessed(0) , mNumPlatformPioProcessed(0)
, mNumPlatformRaReceived(0) , mNumPlatformRaReceived(0)
, mLastPlatformRaTime(0) , mLastPlatformRaTime(0)
@@ -2636,6 +2639,8 @@ void RoutingManager::PdPrefixManager::WithdrawPrefix(void)
LogInfo("Withdrew DHCPv6 PD prefix %s", mPrefix.GetPrefix().ToString().AsCString()); LogInfo("Withdrew DHCPv6 PD prefix %s", mPrefix.GetPrefix().ToString().AsCString());
mPrefix.Clear(); mPrefix.Clear();
mConflicted = false;
mTimer.Stop(); mTimer.Stop();
Get<RoutingManager>().ScheduleRoutingPolicyEvaluation(kImmediately); Get<RoutingManager>().ScheduleRoutingPolicyEvaluation(kImmediately);
@@ -2727,7 +2732,10 @@ void RoutingManager::PdPrefixManager::ApplyFavoredPrefix(const PdPrefix &aFavore
if (aFavoredPrefix.IsFavoredOver(mPrefix)) if (aFavoredPrefix.IsFavoredOver(mPrefix))
{ {
mPrefix = aFavoredPrefix; mPrefix = aFavoredPrefix;
LogInfo("DHCPv6 PD prefix set to %s", mPrefix.GetPrefix().ToString().AsCString()); LogInfo("DHCPv6 PD prefix set to %s", mPrefix.GetPrefix().ToString().AsCString());
CheckConflictWithOnLinkPrefixes();
Get<RoutingManager>().ScheduleRoutingPolicyEvaluation(kImmediately); Get<RoutingManager>().ScheduleRoutingPolicyEvaluation(kImmediately);
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
@@ -2783,6 +2791,35 @@ exit:
return; 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<RxRaTracker>().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<RoutingManager>().ScheduleRoutingPolicyEvaluation(kImmediately);
exit:
return;
}
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
void RoutingManager::PdPrefixManager::HandleRecordHistoryTask(void) void RoutingManager::PdPrefixManager::HandleRecordHistoryTask(void)
@@ -961,8 +961,10 @@ private:
void Start(void) { Evaluate(); } void Start(void) { Evaluate(); }
void Stop(void) { Evaluate(); } void Stop(void) { Evaluate(); }
bool HasPrefix(void) const { return !mPrefix.IsEmpty(); } bool HasPrefix(void) const { return !mPrefix.IsEmpty(); }
bool HasConflictWithOnLinkPrefixes(void) const { return mConflicted; }
const Ip6::Prefix &GetPrefix(void) const { return mPrefix.GetPrefix(); } const Ip6::Prefix &GetPrefix(void) const { return mPrefix.GetPrefix(); }
State GetState(void) const { return mState; } State GetState(void) const { return mState; }
void CheckConflictWithOnLinkPrefixes(void);
void ProcessPrefixesFromRa(const InfraIf::Icmp6Packet &aRaPacket); void ProcessPrefixesFromRa(const InfraIf::Icmp6Packet &aRaPacket);
void ProcessPrefix(const Dhcp6PdPrefix &aPrefix); void ProcessPrefix(const Dhcp6PdPrefix &aPrefix);
@@ -1000,6 +1002,7 @@ private:
#endif #endif
State mState; State mState;
bool mConflicted;
uint32_t mNumPlatformPioProcessed; uint32_t mNumPlatformPioProcessed;
uint32_t mNumPlatformRaReceived; uint32_t mNumPlatformRaReceived;
TimeMilli mLastPlatformRaTime; TimeMilli mLastPlatformRaTime;
+30
View File
@@ -856,6 +856,16 @@ void RxRaTracker::Evaluate(void)
mEventTask.Post(); 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<RoutingManager>().mPdPrefixManager.CheckConflictWithOnLinkPrefixes();
#endif
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Schedule timers // Schedule timers
@@ -1176,6 +1186,26 @@ exit:
return isOnLink; 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 bool RxRaTracker::IsAddressReachableThroughExplicitRoute(const Ip6::Address &aAddress) const
{ {
// Checks whether the `aAddress` matches any discovered route // Checks whether the `aAddress` matches any discovered route
+10
View File
@@ -307,6 +307,16 @@ public:
*/ */
bool IsAddressReachableThroughExplicitRoute(const Ip6::Address &aAddress) const; 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 // Callbacks notifying of changes
void HandleLocalOnLinkPrefixChanged(void); void HandleLocalOnLinkPrefixChanged(void);
+121
View File
@@ -5101,6 +5101,126 @@ void TestDhcp6Pd(void)
FinalizeTest(); 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<BorderRouter::RoutingManager>().SetDhcp6PdEnabled(true);
SuccessOrQuit(sInstance->Get<BorderRouter::RoutingManager>().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<BorderRouter::RoutingManager>().SetEnabled(false));
AdvanceTime(3000);
VerifyOrQuit(sHeapAllocatedPtrs.GetLength() <= heapAllocations);
Log("End of TestDhcp6PdConflict");
FinalizeTest();
}
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE #endif // OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
static void HandleRdnssChanged(void *aContext) static void HandleRdnssChanged(void *aContext)
@@ -5378,6 +5498,7 @@ int main(void)
#endif #endif
#if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE
ot::TestDhcp6Pd(); ot::TestDhcp6Pd();
ot::TestDhcp6PdConflict();
#endif #endif
ot::TestRdnss(); ot::TestRdnss();