[border-router] detect DHCPv6-PD prefix conflict with route prefixes (#12361)

This commit enhances the DHCPv6 PD prefix conflict detection logic
to check against Route Information Options (RIOs) present in received
Router Advertisements, in addition to the existing check against
on-link prefixes (PIOs).

The conflict detection behavior is event-driven to correctly handle
network propagation delays and valid advertisements:

1. On new prefix assignment (`kPdPrefixChanged`): A strict check is
   performed. If the prefix matches any existing RIO from another
   router, it is flagged as a conflict.

2. On RA table updates (`kRxRaPrefixTableChanged`): The check focuses
   on conflict resolution. Crucially, it ignores new RIO matches
   appearing after the prefix has been adopted. This is necessary
   because once the BR publishes the PD prefix in Thread Network
   Data as the OMR prefix, other BRs will naturally start
   advertising it as a RIO to announce reachability.

The unit test `TestDhcp6PdConflict` is updated to verify both the
detection of conflict, its resolution, and that the subsequent RIO
advertisements do not cause a conflict after PD prefix is published
as OMR.
This commit is contained in:
Abtin Keshavarzian
2026-02-04 12:55:55 -08:00
committed by GitHub
parent 848918df32
commit dd0d8311f8
5 changed files with 224 additions and 22 deletions
+113
View File
@@ -5209,6 +5209,119 @@ void TestDhcp6PdConflict(void)
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(200 * 1000);
ReportPdPrefixesAsRa({Pio(pdPrefix, kValidLitime, kPreferredLifetime)});
AdvanceTime(200 * 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.
AdvanceTime(100 * 1000);
VerifyPdOmrPrefix(pdPrefix);
VerifyOmrPrefixInNetData(pdPrefix, /* aDefaultRoute */ false);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Remove the PD prefix
ReportPdPrefixesAsRa({Pio(pdPrefix, 0, 0)});
AdvanceTime(1 * 1000);
VerifyNoPdOmrPrefix();
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Now Advertise the PD prefix as RIO from a router first.
Log("Router A advertises PD prefix as RIO before delegating the prefix");
SendRouterAdvert(routerAddressA, {Rio(pdPrefix, kValidLitime, NetworkData::kRoutePreferenceMedium)});
// Check that local OMR is used.
sExpectedRios.Clear();
sExpectedRios.Add(localOmr);
AdvanceTime(10 * 1000);
VerifyOrQuit(sExpectedRios.SawAll());
VerifyOmrPrefixInNetData(localOmr, /* aDefaultRoute */ false);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Report the same PD prefix. Validate that local OMR prefix is
// still being used (due to RIO conflict).
Log("Delegate PD prefix which conflicts with already advertised RIO prefix from router A");
ReportPdPrefixesAsRa({Pio(pdPrefix, kValidLitime, kPreferredLifetime)});
sExpectedRios.Clear();
sExpectedRios.Add(localOmr);
AdvanceTime(30 * 1000);
VerifyOrQuit(sExpectedRios.SawAll());
VerifyOmrPrefixInNetData(localOmr, /* aDefaultRoute */ false);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Remove the RIO from Router A. Validate that the PD prefix is
// now accepted and used as OMR.
Log("Router A removes RIO, conflict should be resolved");
SendRouterAdvert(routerAddressA, {Rio(pdPrefix, 0, NetworkData::kRoutePreferenceMedium)});
sExpectedRios.Clear();
sExpectedRios.Add(pdPrefix);
AdvanceTime(10 * 1000);
VerifyOrQuit(sExpectedRios.SawAll());
VerifyOmrPrefixInNetData(pdPrefix, /* aDefaultRoute */ false);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Remove the PD prefix
ReportPdPrefixesAsRa({Pio(pdPrefix, 0, 0)});
AdvanceTime(1 * 1000);
VerifyNoPdOmrPrefix();
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Report the PD prefix again. Validate that it is used as OMR prefix.
Log("Delegate PD prefix");
ReportPdPrefixesAsRa({Pio(pdPrefix, kValidLitime, kPreferredLifetime)});
sExpectedRios.Clear();
sExpectedRios.Add(pdPrefix);
AdvanceTime(10 * 1000);
VerifyOrQuit(sExpectedRios.SawAll());
VerifyOmrPrefixInNetData(pdPrefix, /* aDefaultRoute */ false);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Advertise the same prefix as RIO from a router. This should NOT
// be treated as a conflict since the PD prefix is already adopted
// and used.
Log("Router A advertises PD prefix as RIO after it is delegated");
SendRouterAdvert(routerAddressA, {Rio(pdPrefix, kValidLitime, NetworkData::kRoutePreferenceMedium)});
AdvanceTime(1 * 1000);
sExpectedRios.Clear();
sExpectedRios.Add(pdPrefix);
AdvanceTime(300 * 1000);
VerifyOrQuit(sExpectedRios.SawAll());
VerifyOmrPrefixInNetData(pdPrefix, /* aDefaultRoute */ false);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SuccessOrQuit(sInstance->Get<BorderRouter::RoutingManager>().SetEnabled(false));