summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/android/net/dhcp/DhcpClient.java6
-rw-r--r--src/android/net/ip/IpClient.java26
-rw-r--r--tests/integration/src/android/net/ip/IpClientIntegrationTest.java93
3 files changed, 113 insertions, 12 deletions
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index 5564503..4404273 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -503,9 +503,9 @@ public class DhcpClient extends StateMachine {
* check whether or not to support caching the last lease info and INIT-REBOOT state.
*
* INIT-REBOOT state is supported on Android R by default if there is no experiment flag set to
- * disable this feature explicitly, meanwhile we still hope to be able to control this feature
- * on/off by pushing experiment flag for A/B testing and metrics collection on both of Android
- * Q and R version, however it's disbled on Android Q by default.
+ * disable this feature explicitly, meanwhile turning this feature on/off by pushing experiment
+ * flag makes it possible to do A/B test and metrics collection on both of Android Q and R, but
+ * it's disabled on Android Q by default.
*/
public boolean isDhcpLeaseCacheEnabled() {
final boolean defaultEnabled =
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 018d6ab..629d216 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -482,6 +482,7 @@ public class IpClient extends StateMachine {
private boolean mMulticastFiltering;
private long mStartTimeMillis;
private MacAddress mCurrentBssid;
+ private boolean mHasDisabledIPv6OnProvLoss;
/**
* Reading the snapshot is an asynchronous operation initiated by invoking
@@ -1137,9 +1138,9 @@ public class IpClient extends StateMachine {
// Note that we can still be disconnected by IpReachabilityMonitor
// if the IPv6 default gateway (but not the IPv6 DNS servers; see
// accompanying code in IpReachabilityMonitor) is unreachable.
- final boolean ignoreIPv6ProvisioningLoss =
- mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
- && !mCm.shouldAvoidBadWifi();
+ final boolean ignoreIPv6ProvisioningLoss = mHasDisabledIPv6OnProvLoss
+ || (mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker
+ && !mCm.shouldAvoidBadWifi());
// Additionally:
//
@@ -1163,7 +1164,23 @@ public class IpClient extends StateMachine {
// IPv6 default route then also consider the loss of that default route
// to be a loss of provisioning. See b/27962810.
if (oldLp.hasGlobalIpv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
- delta = PROV_CHANGE_LOST_PROVISIONING;
+ // Although link properties have lost IPv6 default route in this case, if IPv4 is still
+ // working with appropriate routes and DNS servers, we can keep the current connection
+ // without disconnecting from the network, just disable IPv6 on that given network until
+ // to the next provisioning. Disabling IPv6 will result in all IPv6 connectivity torn
+ // down and all IPv6 sockets being closed, the non-routable IPv6 DNS servers will be
+ // stripped out, so applications will be able to reconnect immediately over IPv4. See
+ // b/131781810.
+ if (newLp.isIpv4Provisioned()) {
+ mInterfaceCtrl.disableIPv6();
+ mHasDisabledIPv6OnProvLoss = true;
+ delta = PROV_CHANGE_STILL_PROVISIONED;
+ if (DBG) {
+ mLog.log("Disable IPv6 stack completely when the default router has gone");
+ }
+ } else {
+ delta = PROV_CHANGE_LOST_PROVISIONING;
+ }
}
return delta;
@@ -1591,6 +1608,7 @@ public class IpClient extends StateMachine {
@Override
public void enter() {
stopAllIP();
+ mHasDisabledIPv6OnProvLoss = false;
mLinkObserver.clearInterfaceParams();
resetLinkProperties();
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
index c6eb631..53297fe 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
@@ -171,6 +171,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Random;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -994,7 +995,7 @@ public class IpClientIntegrationTest {
assertEquals(5, packetList.size());
assertArpProbe(packetList.get(0));
assertArpAnnounce(packetList.get(3));
-
+ } else {
verifyIPv4OnlyProvisioningSuccess(Collections.singletonList(CLIENT_ADDR));
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime,
TEST_DEFAULT_MTU);
@@ -1245,11 +1246,11 @@ public class IpClientIntegrationTest {
fail("No router solicitation received on interface within timeout");
}
- private void sendBasicRouterAdvertisement(boolean waitForRs) throws Exception {
+ private void sendRouterAdvertisement(boolean waitForRs, short lifetime) throws Exception {
final String dnsServer = "2001:4860:4860::64";
final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64");
ByteBuffer rdnss = buildRdnssOption(3600, dnsServer);
- ByteBuffer ra = buildRaPacket(pio, rdnss);
+ ByteBuffer ra = buildRaPacket(lifetime, pio, rdnss);
if (waitForRs) {
waitForRouterSolicitation();
@@ -1258,6 +1259,14 @@ public class IpClientIntegrationTest {
mPacketReader.sendResponse(ra);
}
+ private void sendBasicRouterAdvertisement(boolean waitForRs) throws Exception {
+ sendRouterAdvertisement(waitForRs, (short) 1800);
+ }
+
+ private void sendRouterAdvertisementWithZeroLifetime() throws Exception {
+ sendRouterAdvertisement(false /* waitForRs */, (short) 0);
+ }
+
// TODO: move this and the following method to a common location and use them in ApfTest.
private static ByteBuffer buildPioOption(int valid, int preferred, String prefixString)
throws Exception {
@@ -1319,7 +1328,8 @@ public class IpClientIntegrationTest {
return checksumAdjust(checksum, (short) IPPROTO_TCP, (short) IPPROTO_ICMPV6);
}
- private static ByteBuffer buildRaPacket(ByteBuffer... options) throws Exception {
+ private static ByteBuffer buildRaPacket(short lifetime, ByteBuffer... options)
+ throws Exception {
final MacAddress srcMac = MacAddress.fromString("33:33:00:00:00:01");
final MacAddress dstMac = MacAddress.fromString("01:02:03:04:05:06");
final byte[] routerLinkLocal = InetAddresses.parseNumericAddress("fe80::1").getAddress();
@@ -1347,7 +1357,7 @@ public class IpClientIntegrationTest {
packet.putShort((short) 0); // Checksum, TBD
packet.put((byte) 0); // Hop limit, unspecified
packet.put((byte) 0); // M=0, O=0
- packet.putShort((short) 1800); // Router lifetime
+ packet.putShort(lifetime); // Router lifetime
packet.putInt(0); // Reachable time, unspecified
packet.putInt(100); // Retrans time 100ms.
@@ -1367,6 +1377,10 @@ public class IpClientIntegrationTest {
return packet;
}
+ private static ByteBuffer buildRaPacket(ByteBuffer... options) throws Exception {
+ return buildRaPacket((short) 1800, options);
+ }
+
private void disableIpv6ProvisioningDelays() throws Exception {
// Speed up the test by disabling DAD and removing router_solicitation_delay.
// We don't need to restore the default value because the interface is removed in tearDown.
@@ -2153,4 +2167,73 @@ public class IpClientIntegrationTest {
doDhcpRoamingTest(true /* hasMismatchedIpAddress */, "\"0001docomo\"" /* display name */,
TEST_DHCP_ROAM_SSID, TEST_DEFAULT_BSSID, true /* expectRoaming */);
}
+
+ private void doDualStackProvisioning() throws Exception {
+ when(mCm.shouldAvoidBadWifi()).thenReturn(true);
+
+ final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
+ .withoutIpReachabilityMonitor()
+ .build();
+ // Accelerate DHCP handshake to shorten test duration, not strictly necessary.
+ mDependencies.setDhcpRapidCommitEnabled(true);
+ mIpc.startProvisioning(config);
+
+ final InOrder inOrder = inOrder(mCb);
+ final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
+ final String dnsServer = "2001:4860:4860::64";
+ final ByteBuffer pio = buildPioOption(3600, 1800, "2001:db8:1::/64");
+ final ByteBuffer rdnss = buildRdnssOption(3600, dnsServer);
+ final ByteBuffer ra = buildRaPacket(pio, rdnss);
+
+ doIpv6OnlyProvisioning(inOrder, ra);
+
+ // 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).atLeastOnce()).onLinkPropertiesChange(argThat(x -> {
+ if (!x.isIpv4Provisioned() || !x.isIpv6Provisioned()) return false;
+ lpFuture.complete(x);
+ return true;
+ }));
+
+ final LinkProperties lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertNotNull(lp);
+ assertTrue(lp.getDnsServers().contains(InetAddress.getByName(dnsServer)));
+ assertTrue(lp.getDnsServers().contains(SERVER_ADDR));
+
+ reset(mCb);
+ }
+
+ @Test
+ public void testIgnoreIpv6ProvisioningLoss() throws Exception {
+ doDualStackProvisioning();
+
+ final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
+
+ // Send RA with 0-lifetime and wait until all IPv6-related default route and DNS servers
+ // have been removed, then verify if there is IPv4-only info left in the LinkProperties.
+ sendRouterAdvertisementWithZeroLifetime();
+ verify(mCb, timeout(TEST_TIMEOUT_MS).atLeastOnce()).onLinkPropertiesChange(
+ argThat(x -> {
+ final boolean isOnlyIPv4Provisioned = (x.getLinkAddresses().size() == 1
+ && x.getDnsServers().size() == 1
+ && x.getAddresses().get(0) instanceof Inet4Address
+ && x.getDnsServers().get(0) instanceof Inet4Address);
+
+ if (!isOnlyIPv4Provisioned) return false;
+ lpFuture.complete(x);
+ return true;
+ }));
+ final LinkProperties lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertNotNull(lp);
+ assertEquals(lp.getAddresses().get(0), CLIENT_ADDR);
+ assertEquals(lp.getDnsServers().get(0), SERVER_ADDR);
+ }
+
+ @Test
+ public void testDualStackProvisioning() throws Exception {
+ doDualStackProvisioning();
+
+ verify(mCb, never()).onProvisioningFailure(any());
+ }
}