Start DHCPv6 PD when no available IPv6 addresses after timeout.
To prevent DHCPv6 PD from potentially disrupting existing IPv6 networks,
only start DHCPv6 PD when device has only an IPv6 default route and no
addresses except IPv6 link-local address after a timeout (5s), basically
it indicates that current network has a clear signal that cannot provide
autoconf. Otherwise, once we start DHCPv6 PD, it may exhaust all
available IPv6 prefixes on that network.
Bug: 260934173
Test: atest NetworkStackIntegrationTests
Change-Id: I2a88b544163385fc0bbfb6168af336a7fddc16d9
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 42d177f..c30b7f9 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -2912,10 +2912,17 @@
break;
case EVENT_IPV6_AUTOCONF_TIMEOUT:
- if (mLinkProperties.isIpv6Provisioned()) break;
- Log.d(mTag, "Fail to get IPv6 address via autoconf, "
- + "start DHCPv6 Prefix Delegation");
- startDhcp6PrefixDelegation();
+ // Only enable DHCPv6 PD on networks that support IPv6 but not autoconf. The
+ // right way to do it is to use the P flag, once it's defined. For now, assume
+ // that the network doesn't support autoconf if it provides an IPv6 default
+ // route but no addresses via an RA.
+ // TODO: leverage the P flag in RA to determine if starting DHCPv6 PD or not,
+ // which is more clear and straightforward.
+ if (!hasIpv6Address(mLinkProperties)
+ && mLinkProperties.hasIpv6DefaultRoute()) {
+ Log.d(TAG, "Network supports IPv6 but not autoconf, starting DHCPv6 PD");
+ startDhcp6PrefixDelegation();
+ }
break;
case DhcpClient.CMD_PRE_DHCP_ACTION:
@@ -3064,16 +3071,28 @@
mCallback.setMaxDtimMultiplier(multiplier);
}
+ /**
+ * Check if current LinkProperties has either global IPv6 address or ULA (i.e. non IPv6
+ * link-local addres).
+ *
+ * This function can be used to derive the DTIM multiplier per current network situation or
+ * decide if we should start DHCPv6 Prefix Delegation when no IPv6 addresses are available
+ * after autoconf timeout(5s).
+ */
+ private static boolean hasIpv6Address(@NonNull final LinkProperties lp) {
+ return CollectionUtils.any(lp.getLinkAddresses(),
+ la -> {
+ final InetAddress address = la.getAddress();
+ return (address instanceof Inet6Address) && !address.isLinkLocalAddress();
+ });
+ }
+
private int deriveDtimMultiplier() {
final boolean hasIpv4Addr = mLinkProperties.hasIpv4Address();
// For a host in the network that has only ULA and link-local but no GUA, consider
// that it also has IPv6 connectivity. LinkProperties#isIpv6Provisioned only returns
// true when it has a GUA, so we cannot use it for IPv6-only network case.
- final boolean hasIpv6Addr = CollectionUtils.any(mLinkProperties.getLinkAddresses(),
- la -> {
- final InetAddress address = la.getAddress();
- return (address instanceof Inet6Address) && !address.isLinkLocalAddress();
- });
+ final boolean hasIpv6Addr = hasIpv6Address(mLinkProperties);
final int multiplier;
if (!mMulticastFiltering) {
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
index 39eb727..c9863d0 100644
--- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -4793,25 +4793,68 @@
verify(mCb, never()).onProvisioningSuccess(any());
}
- @Test
- public void testDhcp6Pd_notStart() throws Exception {
- final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64");
- final ByteBuffer rdnss = buildRdnssOption(3600, "2001:4860:4860::64");
- final ByteBuffer slla = buildSllaOption();
- final ByteBuffer ra = buildRaPacket(pio, rdnss, slla);
+ private void runDhcp6PdNotStartInDualStackTest(final String prefix, final String dnsServer)
+ throws Exception {
+ final List<ByteBuffer> options = new ArrayList<>();
+ if (prefix != null) {
+ options.add(buildPioOption(3600, 1800, prefix));
+ }
+ if (dnsServer != null) {
+ options.add(buildRdnssOption(3600, dnsServer));
+ }
+ options.add(buildSllaOption());
+ final ByteBuffer ra = buildRaPacket(options.toArray(new ByteBuffer[options.size()]));
ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
- .withoutIPv4()
.build();
+ setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */,
+ false /* isDhcpIpConflictDetectEnabled */, false /* isIPv6OnlyPreferredEnabled */);
startIpClientProvisioning(config);
waitForRouterSolicitation();
mPacketReader.sendResponse(ra);
- // Response an normal RA for IPv6 provisioning, then DHCPv6 prefix delegation
- // should not start.
+ // Start IPv4 provisioning and wait until entire provisioning completes.
+ handleDhcpPackets(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
+ true /* shouldReplyRapidCommitAck */, TEST_DEFAULT_MTU, null /* serverSentUrl */);
+ verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(any());
+ }
+
+ @Test
+ public void testDhcp6Pd_notStartWithGlobalPio() throws Exception {
+ runDhcp6PdNotStartInDualStackTest("2001:db8:1::/64" /* prefix */,
+ "2001:4860:4860::64" /* dnsServer */);
+ // Reply with a normal RA with global prefix and an off-link DNS for IPv6 provisioning,
+ // DHCPv6 prefix delegation should not start.
assertNull(getNextDhcp6Packet(PACKET_TIMEOUT_MS));
- verify(mCb).onProvisioningSuccess(any());
+ }
+
+ @Test
+ public void testDhcp6Pd_notStartWithUlaPioAndDns() throws Exception {
+ runDhcp6PdNotStartInDualStackTest("fd7c:9df8:7f39:dc89::/64" /* prefix */,
+ "fd7c:9df8:7f39:dc89::1" /* dnsServer */);
+ // Reply with a normal RA even with ULA prefix and on-link ULA DNS for IPv6 provisioning,
+ // DHCPv6 prefix delegation should not start.
+ assertNull(getNextDhcp6Packet(PACKET_TIMEOUT_MS));
+ }
+
+ @Test
+ public void testDhcp6Pd_notStartWithUlaPioAndOffLinkDns() throws Exception {
+ runDhcp6PdNotStartInDualStackTest("fd7c:9df8:7f39:dc89::/64" /* prefix */,
+ "2001:4860:4860::64" /* dnsServer */);
+ // Reply with a normal RA even with ULA prefix and off-link DNS for IPv6 provisioning,
+ // DHCPv6 prefix delegation should not start.
+ assertNull(getNextDhcp6Packet(PACKET_TIMEOUT_MS));
+ }
+
+ @Test
+ public void testDhcp6Pd_startWithNoNonIpv6LinkLocalAddresses() throws Exception {
+ runDhcp6PdNotStartInDualStackTest(null /* prefix */,
+ "2001:4860:4860::64" /* dnsServer */);
+ // Reply with a normal RA with only RDNSS but no PIO for IPv6 provisioning,
+ // DHCPv6 prefix delegation should start.
+ final Dhcp6Packet packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS);
+ assertTrue(packet instanceof Dhcp6SolicitPacket);
}
@Test