Loading src/android/net/ip/IpClient.java +46 −11 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.net.ip; import static android.net.RouteInfo.RTN_UNICAST; import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable; import static android.net.util.NetworkStackUtils.IPCLIENT_DISABLE_ACCEPT_RA_VERSION; import static android.net.util.NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION; import static android.net.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION; import static android.net.util.SocketUtils.makePacketSocketAddress; Loading Loading @@ -527,7 +528,7 @@ public class IpClient extends StateMachine { private boolean mMulticastFiltering; private long mStartTimeMillis; private MacAddress mCurrentBssid; private boolean mHasDisabledIPv6OnProvLoss; private boolean mHasDisabledIpv6OrAcceptRaOnProvLoss; /** * Reading the snapshot is an asynchronous operation initiated by invoking Loading Loading @@ -881,6 +882,11 @@ public class IpClient extends StateMachine { } } private boolean shouldDisableAcceptRaOnProvisioningLoss() { return mDependencies.isFeatureEnabled(mContext, IPCLIENT_DISABLE_ACCEPT_RA_VERSION, true /* defaultEnabled */); } @Override protected void onQuitting() { mCallback.onQuit(); Loading Loading @@ -1198,6 +1204,21 @@ public class IpClient extends StateMachine { return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes()); } private void setIpv6AcceptRa(int acceptRa) { try { mNetd.setProcSysNet(INetd.IPV6, INetd.CONF, mInterfaceParams.name, "accept_ra", Integer.toString(acceptRa)); } catch (Exception e) { Log.e(mTag, "Failed to set accept_ra to " + acceptRa); } } private void restartIpv6WithAcceptRaDisabled() { mInterfaceCtrl.disableIPv6(); setIpv6AcceptRa(0 /* accept_ra */); startIPv6(); } // TODO: Investigate folding all this into the existing static function // LinkProperties.compareProvisioning() or some other single function that // takes two LinkProperties objects and returns a ProvisioningChange Loading Loading @@ -1247,7 +1268,7 @@ 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 = mHasDisabledIPv6OnProvLoss final boolean ignoreIPv6ProvisioningLoss = mHasDisabledIpv6OrAcceptRaOnProvLoss || (mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker && !mCm.shouldAvoidBadWifi()); Loading Loading @@ -1275,18 +1296,31 @@ public class IpClient extends StateMachine { if (oldLp.hasGlobalIpv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) { // 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. // without disconnecting from the network, just disable IPv6 or accept_ra parameter on // that given network until to the next provisioning. // // Disabling IPv6 stack 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. // // Sometimes disabling IPv6 stack might introduce other issues(see b/179222860), // instead disabling accept_ra will result in only IPv4 provisioning and IPv6 link // local address left on the interface, so applications will be able to reconnect // immediately over IPv4 and keep IPv6 link-local capable. if (newLp.isIpv4Provisioned()) { if (shouldDisableAcceptRaOnProvisioningLoss()) { restartIpv6WithAcceptRaDisabled(); } else { mInterfaceCtrl.disableIPv6(); } mNetworkQuirkMetrics.setEvent(NetworkQuirkEvent.QE_IPV6_PROVISIONING_ROUTER_LOST); mNetworkQuirkMetrics.statsWrite(); mHasDisabledIPv6OnProvLoss = true; mHasDisabledIpv6OrAcceptRaOnProvLoss = true; delta = PROV_CHANGE_STILL_PROVISIONED; mLog.log("Disable IPv6 stack completely when the default router has gone"); mLog.log(shouldDisableAcceptRaOnProvisioningLoss() ? "Disabled accept_ra parameter " : "Disabled IPv6 stack completely " + "when the IPv6 default router has gone"); } else { delta = PROV_CHANGE_LOST_PROVISIONING; } Loading Loading @@ -1839,7 +1873,8 @@ public class IpClient extends StateMachine { @Override public void enter() { stopAllIP(); mHasDisabledIPv6OnProvLoss = false; setIpv6AcceptRa(2 /* accept_ra */); mHasDisabledIpv6OrAcceptRaOnProvLoss = false; mGratuitousNaTargetAddresses.clear(); mLinkObserver.clearInterfaceParams(); Loading src/android/net/util/NetworkStackUtils.java +6 −0 Original line number Diff line number Diff line Loading @@ -249,6 +249,12 @@ public class NetworkStackUtils { public static final String IPCLIENT_GARP_NA_ROAMING_VERSION = "ipclient_garp_na_roaming_version"; /** * Experiment flag to disable accept_ra parameter when IPv6 provisioning loss happens due to * the default route has gone. */ public static final String IPCLIENT_DISABLE_ACCEPT_RA_VERSION = "ipclient_disable_accept_ra"; static { System.loadLibrary("networkstackutilsjni"); } Loading tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java +46 −7 Original line number Diff line number Diff line Loading @@ -2404,12 +2404,15 @@ public abstract class IpClientIntegrationTestCommon { reset(mCb); } private void doDualStackProvisioning() throws Exception { private void doDualStackProvisioning(boolean shouldDisableAcceptRa) throws Exception { when(mCm.shouldAvoidBadWifi()).thenReturn(true); final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .build(); setFeatureEnabled(NetworkStackUtils.IPCLIENT_DISABLE_ACCEPT_RA_VERSION, shouldDisableAcceptRa); // Enable rapid commit to accelerate DHCP handshake to shorten test duration, // not strictly necessary. setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, Loading @@ -2419,9 +2422,9 @@ public abstract class IpClientIntegrationTestCommon { performDualStackProvisioning(); } @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required") public void testIgnoreIpv6ProvisioningLoss() throws Exception { doDualStackProvisioning(); @Test @SignatureRequiredTest(reason = "signature perms are required due to mocked callabck") public void testIgnoreIpv6ProvisioningLoss_disableIPv6Stack() throws Exception { doDualStackProvisioning(false /* shouldDisableAcceptRa */); final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>(); Loading Loading @@ -2450,9 +2453,45 @@ public abstract class IpClientIntegrationTestCommon { (long) NetworkQuirkEvent.QE_IPV6_PROVISIONING_ROUTER_LOST.ordinal()); } @Test @SignatureRequiredTest(reason = "signature perms are required due to mocked callabck") public void testIgnoreIpv6ProvisioningLoss_disableAcceptRa() throws Exception { doDualStackProvisioning(true /* shouldDisableAcceptRa */); final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>(); // Send RA with 0-lifetime and wait until all global IPv6 addresses, IPv6-related default // route and DNS servers have been removed, then verify if there is IPv4-only, IPv6 link // local address and route to fe80::/64 info left in the LinkProperties. sendRouterAdvertisementWithZeroLifetime(); verify(mCb, timeout(TEST_TIMEOUT_MS).atLeastOnce()).onLinkPropertiesChange( argThat(x -> { // Only IPv4 provisioned and IPv6 link-local address final boolean isIPv6LinkLocalAndIPv4OnlyProvisioned = (x.getLinkAddresses().size() == 2 && x.getDnsServers().size() == 1 && x.getAddresses().get(0) instanceof Inet4Address && x.getDnsServers().get(0) instanceof Inet4Address); if (!isIPv6LinkLocalAndIPv4OnlyProvisioned) 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); assertTrue(lp.getAddresses().get(1).isLinkLocalAddress()); reset(mCb); // Send an RA to verify that global IPv6 addresses won't be configured on the interface. sendBasicRouterAdvertisement(false /* waitForRs */); verify(mCb, timeout(TEST_TIMEOUT_MS).times(0)).onLinkPropertiesChange(any()); } @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required") public void testDualStackProvisioning() throws Exception { doDualStackProvisioning(); doDualStackProvisioning(false /* shouldDisableAcceptRa */); verify(mCb, never()).onProvisioningFailure(any()); } Loading Loading @@ -2672,7 +2711,7 @@ public abstract class IpClientIntegrationTestCommon { public void testNoFdLeaks() throws Exception { // Shut down and restart IpClient once to ensure that any fds that are opened the first // time it runs do not cause the test to fail. doDualStackProvisioning(); doDualStackProvisioning(false /* shouldDisableAcceptRa */); shutdownAndRecreateIpClient(); // Unfortunately we cannot use a large number of iterations as it would make the test run Loading @@ -2680,7 +2719,7 @@ public abstract class IpClientIntegrationTestCommon { final int iterations = 10; final int before = getNumOpenFds(); for (int i = 0; i < iterations; i++) { doDualStackProvisioning(); doDualStackProvisioning(false /* shouldDisableAcceptRa */); shutdownAndRecreateIpClient(); // The last time this loop runs, mIpc will be shut down in tearDown. } Loading Loading
src/android/net/ip/IpClient.java +46 −11 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.net.ip; import static android.net.RouteInfo.RTN_UNICAST; import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable; import static android.net.util.NetworkStackUtils.IPCLIENT_DISABLE_ACCEPT_RA_VERSION; import static android.net.util.NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION; import static android.net.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION; import static android.net.util.SocketUtils.makePacketSocketAddress; Loading Loading @@ -527,7 +528,7 @@ public class IpClient extends StateMachine { private boolean mMulticastFiltering; private long mStartTimeMillis; private MacAddress mCurrentBssid; private boolean mHasDisabledIPv6OnProvLoss; private boolean mHasDisabledIpv6OrAcceptRaOnProvLoss; /** * Reading the snapshot is an asynchronous operation initiated by invoking Loading Loading @@ -881,6 +882,11 @@ public class IpClient extends StateMachine { } } private boolean shouldDisableAcceptRaOnProvisioningLoss() { return mDependencies.isFeatureEnabled(mContext, IPCLIENT_DISABLE_ACCEPT_RA_VERSION, true /* defaultEnabled */); } @Override protected void onQuitting() { mCallback.onQuit(); Loading Loading @@ -1198,6 +1204,21 @@ public class IpClient extends StateMachine { return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes()); } private void setIpv6AcceptRa(int acceptRa) { try { mNetd.setProcSysNet(INetd.IPV6, INetd.CONF, mInterfaceParams.name, "accept_ra", Integer.toString(acceptRa)); } catch (Exception e) { Log.e(mTag, "Failed to set accept_ra to " + acceptRa); } } private void restartIpv6WithAcceptRaDisabled() { mInterfaceCtrl.disableIPv6(); setIpv6AcceptRa(0 /* accept_ra */); startIPv6(); } // TODO: Investigate folding all this into the existing static function // LinkProperties.compareProvisioning() or some other single function that // takes two LinkProperties objects and returns a ProvisioningChange Loading Loading @@ -1247,7 +1268,7 @@ 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 = mHasDisabledIPv6OnProvLoss final boolean ignoreIPv6ProvisioningLoss = mHasDisabledIpv6OrAcceptRaOnProvLoss || (mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker && !mCm.shouldAvoidBadWifi()); Loading Loading @@ -1275,18 +1296,31 @@ public class IpClient extends StateMachine { if (oldLp.hasGlobalIpv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) { // 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. // without disconnecting from the network, just disable IPv6 or accept_ra parameter on // that given network until to the next provisioning. // // Disabling IPv6 stack 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. // // Sometimes disabling IPv6 stack might introduce other issues(see b/179222860), // instead disabling accept_ra will result in only IPv4 provisioning and IPv6 link // local address left on the interface, so applications will be able to reconnect // immediately over IPv4 and keep IPv6 link-local capable. if (newLp.isIpv4Provisioned()) { if (shouldDisableAcceptRaOnProvisioningLoss()) { restartIpv6WithAcceptRaDisabled(); } else { mInterfaceCtrl.disableIPv6(); } mNetworkQuirkMetrics.setEvent(NetworkQuirkEvent.QE_IPV6_PROVISIONING_ROUTER_LOST); mNetworkQuirkMetrics.statsWrite(); mHasDisabledIPv6OnProvLoss = true; mHasDisabledIpv6OrAcceptRaOnProvLoss = true; delta = PROV_CHANGE_STILL_PROVISIONED; mLog.log("Disable IPv6 stack completely when the default router has gone"); mLog.log(shouldDisableAcceptRaOnProvisioningLoss() ? "Disabled accept_ra parameter " : "Disabled IPv6 stack completely " + "when the IPv6 default router has gone"); } else { delta = PROV_CHANGE_LOST_PROVISIONING; } Loading Loading @@ -1839,7 +1873,8 @@ public class IpClient extends StateMachine { @Override public void enter() { stopAllIP(); mHasDisabledIPv6OnProvLoss = false; setIpv6AcceptRa(2 /* accept_ra */); mHasDisabledIpv6OrAcceptRaOnProvLoss = false; mGratuitousNaTargetAddresses.clear(); mLinkObserver.clearInterfaceParams(); Loading
src/android/net/util/NetworkStackUtils.java +6 −0 Original line number Diff line number Diff line Loading @@ -249,6 +249,12 @@ public class NetworkStackUtils { public static final String IPCLIENT_GARP_NA_ROAMING_VERSION = "ipclient_garp_na_roaming_version"; /** * Experiment flag to disable accept_ra parameter when IPv6 provisioning loss happens due to * the default route has gone. */ public static final String IPCLIENT_DISABLE_ACCEPT_RA_VERSION = "ipclient_disable_accept_ra"; static { System.loadLibrary("networkstackutilsjni"); } Loading
tests/integration/src/android/net/ip/IpClientIntegrationTestCommon.java +46 −7 Original line number Diff line number Diff line Loading @@ -2404,12 +2404,15 @@ public abstract class IpClientIntegrationTestCommon { reset(mCb); } private void doDualStackProvisioning() throws Exception { private void doDualStackProvisioning(boolean shouldDisableAcceptRa) throws Exception { when(mCm.shouldAvoidBadWifi()).thenReturn(true); final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIpReachabilityMonitor() .build(); setFeatureEnabled(NetworkStackUtils.IPCLIENT_DISABLE_ACCEPT_RA_VERSION, shouldDisableAcceptRa); // Enable rapid commit to accelerate DHCP handshake to shorten test duration, // not strictly necessary. setDhcpFeatures(false /* isDhcpLeaseCacheEnabled */, true /* isRapidCommitEnabled */, Loading @@ -2419,9 +2422,9 @@ public abstract class IpClientIntegrationTestCommon { performDualStackProvisioning(); } @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required") public void testIgnoreIpv6ProvisioningLoss() throws Exception { doDualStackProvisioning(); @Test @SignatureRequiredTest(reason = "signature perms are required due to mocked callabck") public void testIgnoreIpv6ProvisioningLoss_disableIPv6Stack() throws Exception { doDualStackProvisioning(false /* shouldDisableAcceptRa */); final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>(); Loading Loading @@ -2450,9 +2453,45 @@ public abstract class IpClientIntegrationTestCommon { (long) NetworkQuirkEvent.QE_IPV6_PROVISIONING_ROUTER_LOST.ordinal()); } @Test @SignatureRequiredTest(reason = "signature perms are required due to mocked callabck") public void testIgnoreIpv6ProvisioningLoss_disableAcceptRa() throws Exception { doDualStackProvisioning(true /* shouldDisableAcceptRa */); final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>(); // Send RA with 0-lifetime and wait until all global IPv6 addresses, IPv6-related default // route and DNS servers have been removed, then verify if there is IPv4-only, IPv6 link // local address and route to fe80::/64 info left in the LinkProperties. sendRouterAdvertisementWithZeroLifetime(); verify(mCb, timeout(TEST_TIMEOUT_MS).atLeastOnce()).onLinkPropertiesChange( argThat(x -> { // Only IPv4 provisioned and IPv6 link-local address final boolean isIPv6LinkLocalAndIPv4OnlyProvisioned = (x.getLinkAddresses().size() == 2 && x.getDnsServers().size() == 1 && x.getAddresses().get(0) instanceof Inet4Address && x.getDnsServers().get(0) instanceof Inet4Address); if (!isIPv6LinkLocalAndIPv4OnlyProvisioned) 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); assertTrue(lp.getAddresses().get(1).isLinkLocalAddress()); reset(mCb); // Send an RA to verify that global IPv6 addresses won't be configured on the interface. sendBasicRouterAdvertisement(false /* waitForRs */); verify(mCb, timeout(TEST_TIMEOUT_MS).times(0)).onLinkPropertiesChange(any()); } @Test @SignatureRequiredTest(reason = "TODO: evaluate whether signature perms are required") public void testDualStackProvisioning() throws Exception { doDualStackProvisioning(); doDualStackProvisioning(false /* shouldDisableAcceptRa */); verify(mCb, never()).onProvisioningFailure(any()); } Loading Loading @@ -2672,7 +2711,7 @@ public abstract class IpClientIntegrationTestCommon { public void testNoFdLeaks() throws Exception { // Shut down and restart IpClient once to ensure that any fds that are opened the first // time it runs do not cause the test to fail. doDualStackProvisioning(); doDualStackProvisioning(false /* shouldDisableAcceptRa */); shutdownAndRecreateIpClient(); // Unfortunately we cannot use a large number of iterations as it would make the test run Loading @@ -2680,7 +2719,7 @@ public abstract class IpClientIntegrationTestCommon { final int iterations = 10; final int before = getNumOpenFds(); for (int i = 0; i < iterations; i++) { doDualStackProvisioning(); doDualStackProvisioning(false /* shouldDisableAcceptRa */); shutdownAndRecreateIpClient(); // The last time this loop runs, mIpc will be shut down in tearDown. } Loading