[br] do not deprecate old prefix if advertised as preferred by others (#13618)

When `OnLinkPrefixManager` deprecates an old local on-link prefix (e.g.,
following an Extended PAN ID change), it includes the prefix as a PIO
with zero preferred lifetime in emitted Router Advertisements until its
valid lifetime expires.

However, if another router on the infrastructure link is actively
advertising the same prefix as a preferred on-link prefix (with a
non-zero preferred lifetime), continuing to emit deprecating PIOs
creates conflicting advertisements on the link, which can cause hosts to
switch between deprecating and preferring their SLAAC addresses.

This commit adds new logic to check for this:
- Adds `mDeprecateTime` to `OldPrefix` to track when deprecation of an
  old prefix began (and initializes it to the current time when
  restoring old prefixes from persistent storage).
- Introduces `RxRaTracker::HasSeenPreferredOnLinkPrefixAfter()` to check
  whether an RA received after a given timestamp advertises the prefix
  as an on-link and preferred (non-deprecated) prefix.
- In `OnLinkPrefixManager::AppendOldPrefixes()`, skips emitting the
  deprecation PIO if another router has advertised the prefix as
  preferred after `mDeprecateTime`.

This commit also adds the `TestOldOnLinkPrefixAdvertisedOnLink` unit
test in `test_routing_manager` to validate that deprecation PIOs are
suppressed when an external router advertises the prefix as preferred
and resumed if the external router later deprecates it.
This commit is contained in:
Abtin Keshavarzian
2026-09-17 13:47:35 -07:00
committed by GitHub
parent 87e6c37254
commit fa15976e9b
5 changed files with 188 additions and 8 deletions
+16 -4
View File
@@ -1272,8 +1272,9 @@ void RoutingManager::OnLinkPrefixManager::Init(void)
lifetime = Min(savedPrefix.GetLifetime(), Time::MsecToSec(TimerMilli::kMaxDelay));
entry->mPrefix = savedPrefix.GetPrefix();
entry->mExpireTime = now + Time::SecToMsec(lifetime);
entry->mPrefix = savedPrefix.GetPrefix();
entry->mDeprecateTime = now;
entry->mExpireTime = now + Time::SecToMsec(lifetime);
LogInfo("Restored old prefix %s, lifetime:%lu", entry->mPrefix.ToString().AsCString(), ToUlong(lifetime));
@@ -1658,6 +1659,16 @@ Error RoutingManager::OnLinkPrefixManager::AppendOldPrefixes(RouterAdvert::TxMes
continue;
}
// If another router on the infrastructure link is actively
// advertising this prefix as a preferred on-link prefix after we
// started deprecating it, we skip including it as a deprecating
// PIO to avoid sending conflicting advertisements.
if (Get<RxRaTracker>().HasSeenPreferredOnLinkPrefixAfter(oldPrefix.mPrefix, oldPrefix.mDeprecateTime))
{
continue;
}
validLifetime = TimeMilli::MsecToSec(oldPrefix.mExpireTime - now);
flags = PrefixInfoOption::kOnLinkFlag | PrefixInfoOption::kAutoConfigFlag;
@@ -1755,8 +1766,9 @@ void RoutingManager::OnLinkPrefixManager::DeprecateOldPrefix(const Ip6::Prefix &
Get<Settings>().RemoveBrOnLinkPrefix(removedPrefix);
}
entry->mPrefix = aPrefix;
entry->mExpireTime = aExpireTime;
entry->mPrefix = aPrefix;
entry->mDeprecateTime = TimerMilli::GetNow();
entry->mExpireTime = aExpireTime;
mTimer.FireAtIfEarlier(aExpireTime);
SavePrefix(aPrefix, aExpireTime);
@@ -742,6 +742,7 @@ private:
bool Matches(const Ip6::Prefix &aPrefix) const { return mPrefix == aPrefix; }
Ip6::Prefix mPrefix;
TimeMilli mDeprecateTime;
TimeMilli mExpireTime;
};
+21
View File
@@ -1183,6 +1183,27 @@ exit:
return isOnLink;
}
bool RxRaTracker::HasSeenPreferredOnLinkPrefixAfter(const Ip6::Prefix &aPrefix, TimeMilli aTime) const
{
bool hasSeen = false;
for (const Router &router : mRouters)
{
for (const OnLinkPrefix &onLinkPrefix : router.mOnLinkPrefixes)
{
if (!onLinkPrefix.IsDeprecated() && onLinkPrefix.Matches(aPrefix) &&
(onLinkPrefix.GetLastUpdateTime() >= aTime))
{
hasSeen = true;
ExitNow();
}
}
}
exit:
return hasSeen;
}
bool RxRaTracker::IsPrefixOnLink(const Ip6::Prefix &aPrefix) const
{
bool isOnLink = false;
+12
View File
@@ -307,6 +307,18 @@ public:
*/
bool IsAddressReachableThroughExplicitRoute(const Ip6::Address &aAddress) const;
/**
* Indicates whether a given prefix is seen as a preferred (non-deprecated) on-link prefix in any tracked RA
* received after a given time.
*
* @param[in] aPrefix The IPv6 prefix to check.
* @param[in] aTime The time threshold.
*
* @retval TRUE The prefix is seen as a preferred on-link prefix after @p aTime.
* @retval FALSE The prefix is not seen as a preferred on-link prefix after @p aTime.
*/
bool HasSeenPreferredOnLinkPrefixAfter(const Ip6::Prefix &aPrefix, TimeMilli aTime) const;
/**
* Indicates whether a given prefix is seen as an on-link prefix in any tracked RA.
*
+138 -4
View File
@@ -2780,7 +2780,7 @@ void TestLocalOnLinkPrefixDeprecation(void)
void TestUnadvertisedLocalOnLinkPrefix(void)
{
static const otExtendedPanId kExtPanId1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x6, 0x7, 0x08}};
static const otExtendedPanId kExtPanId1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}};
Ip6::Prefix localOnLink;
Ip6::Prefix oldLocalOnLink;
@@ -2931,7 +2931,7 @@ void TestExtPanIdChange(void)
{
static constexpr uint32_t kMaxRaTxInterval = 196; // In seconds
static const otExtendedPanId kExtPanId1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x6, 0x7, 0x08}};
static const otExtendedPanId kExtPanId1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}};
static const otExtendedPanId kExtPanId2 = {{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x99, 0x88}};
static const otExtendedPanId kExtPanId3 = {{0x12, 0x34, 0x56, 0x78, 0x9a, 0xab, 0xcd, 0xef}};
static const otExtendedPanId kExtPanId4 = {{0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00}};
@@ -3437,6 +3437,139 @@ void TestExtPanIdChange(void)
FinalizeTest();
}
void TestOldOnLinkPrefixAdvertisedOnLink(void)
{
static const otExtendedPanId kExtPanId1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}};
Ip6::Prefix localOnLink;
Ip6::Prefix oldLocalOnLink;
Ip6::Prefix localOmr;
Ip6::Address routerAddressA = AddressFromString("fd00::aaaa");
otOperationalDataset dataset;
uint16_t heapAllocations;
Log("--------------------------------------------------------------------------------------------");
Log("TestOldOnLinkPrefixAdvertisedOnLink");
InitTest();
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Start Routing Manager. Check emitted RS and RA messages.
heapAllocations = sHeapAllocatedPtrs.GetLength();
SuccessOrQuit(sInstance->Get<BorderRouter::RoutingManager>().SetEnabled(true));
SuccessOrQuit(sInstance->Get<BorderRouter::RoutingManager>().GetOnLinkPrefix(localOnLink));
SuccessOrQuit(sInstance->Get<BorderRouter::RoutingManager>().GetOmrPrefix(localOmr));
Log("Local on-link prefix is %s", localOnLink.ToString().AsCString());
Log("Local OMR prefix is %s", localOmr.ToString().AsCString());
sRsEmitted = false;
sRaValidated = false;
sExpectedPio = kPioAdvertisingLocalOnLink;
sExpectedRios.Clear();
sExpectedRios.Add(localOmr);
AdvanceTime(30000);
VerifyOrQuit(sRsEmitted);
VerifyOrQuit(sRaValidated);
VerifyOrQuit(sExpectedRios.SawAll());
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Change the extended PAN ID to deprecate the current local on-link prefix.
Log("Changing ext PAN ID");
oldLocalOnLink = localOnLink;
SuccessOrQuit(otDatasetGetActive(sInstance, &dataset));
VerifyOrQuit(dataset.mComponents.mIsExtendedPanIdPresent);
dataset.mExtendedPanId = kExtPanId1;
dataset.mActiveTimestamp.mSeconds++;
SuccessOrQuit(otDatasetSetActive(sInstance, &dataset));
AdvanceTime(500);
SuccessOrQuit(sInstance->Get<BorderRouter::RoutingManager>().GetOnLinkPrefix(localOnLink));
VerifyOrQuit(localOnLink != oldLocalOnLink);
Log("Local on-link prefix changed to %s from %s", localOnLink.ToString().AsCString(),
oldLocalOnLink.ToString().AsCString());
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Validate that the old local on-link prefix is initially included as a
// deprecating PIO in the emitted RA.
sRaValidated = false;
sExpectedPio = kPioAdvertisingLocalOnLink;
AdvanceTime(30000);
VerifyOrQuit(sRaValidated);
VerifyOrQuit(sDeprecatingPrefixes.GetLength() == 1);
VerifyOrQuit(sDeprecatingPrefixes[0].mPrefix == oldLocalOnLink);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Send an RA from router A advertising `oldLocalOnLink` as a preferred on-link prefix.
// Ensure that it is no longer included as a deprecating PIO in emitted RAs.
Log("Router A advertises old on-link prefix as preferred");
SendRouterAdvert(routerAddressA, {Pio(oldLocalOnLink, kValidLitime, kPreferredLifetime)});
sRaValidated = false;
sExpectedPio = kPioAdvertisingLocalOnLink;
AdvanceTime(30000);
VerifyOrQuit(sRaValidated);
VerifyOrQuit(sDeprecatingPrefixes.IsEmpty());
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Send an RA from router A deprecating `oldLocalOnLink` (zero preferred lifetime).
// Ensure that this BR resumes including `oldLocalOnLink` as a deprecating PIO.
Log("Router A deprecates old on-link prefix");
SendRouterAdvert(routerAddressA, {Pio(oldLocalOnLink, kValidLitime, 0)});
sRaValidated = false;
sExpectedPio = kPioAdvertisingLocalOnLink;
AdvanceTime(30000);
VerifyOrQuit(sRaValidated);
VerifyOrQuit(sDeprecatingPrefixes.GetLength() == 1);
VerifyOrQuit(sDeprecatingPrefixes[0].mPrefix == oldLocalOnLink);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Router A advertises `oldLocalOnLink` as preferred again.
// Ensure that the deprecating PIO is suppressed once more.
Log("Router A advertises old on-link prefix as preferred again");
SendRouterAdvert(routerAddressA, {Pio(oldLocalOnLink, kValidLitime, kPreferredLifetime)});
sRaValidated = false;
sExpectedPio = kPioAdvertisingLocalOnLink;
AdvanceTime(30000);
VerifyOrQuit(sRaValidated);
VerifyOrQuit(sDeprecatingPrefixes.IsEmpty());
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SuccessOrQuit(sInstance->Get<BorderRouter::RoutingManager>().SetEnabled(false));
AdvanceTime(3000);
VerifyOrQuit(heapAllocations == sHeapAllocatedPtrs.GetLength());
Log("End of TestOldOnLinkPrefixAdvertisedOnLink");
FinalizeTest();
}
void TestPrefixStaleTime(void)
{
Ip6::Prefix localOnLink;
@@ -3996,7 +4129,7 @@ void TestLearnRaHeader(void)
void TestConflictingPrefix(void)
{
static const otExtendedPanId kExtPanId1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x6, 0x7, 0x08}};
static const otExtendedPanId kExtPanId1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}};
Ip6::Prefix localOnLink;
Ip6::Prefix oldLocalOnLink;
@@ -4219,7 +4352,7 @@ void TestConflictingPrefix(void)
#if OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE
void TestSavedOnLinkPrefixes(void)
{
static const otExtendedPanId kExtPanId1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x6, 0x7, 0x08}};
static const otExtendedPanId kExtPanId1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}};
Ip6::Prefix localOnLink;
Ip6::Prefix oldLocalOnLink;
@@ -5611,6 +5744,7 @@ int main(void)
ot::TestLocalOnLinkPrefixDeprecation();
ot::TestUnadvertisedLocalOnLinkPrefix();
ot::TestExtPanIdChange();
ot::TestOldOnLinkPrefixAdvertisedOnLink();
ot::TestConflictingPrefix();
ot::TestPrefixStaleTime();
ot::TestRouterNsProbe();