Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 690e12eb authored by Xiao Ma's avatar Xiao Ma
Browse files

Disable accept_ra upon IPv6 default route has gone for dual-stack devices.

Disabling IPv6 stack completely when IPv6 provisioning loss happens
due to the default route has gone might introduce other issues, kernel
will drop the IPv6 packets on the non-IPv6 capable interface which is
used in the wifi calling. Instead just disable accept_ra parameter,
assure upcoming RA packets won't be processed on the specific interface,
then there is only IPv4 provisioning and IPv6 link-local info left.

Bug: 179222860
Test: atest NetworkStackIntegratioTest
Change-Id: I03cfe4fd74e6e7c46ae62b73563fb4cd28392bfc
parent 861fe42c
Loading
Loading
Loading
Loading
+46 −11
Original line number Diff line number Diff line
@@ -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;
@@ -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
@@ -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();
@@ -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
@@ -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());

@@ -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;
            }
@@ -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();
+6 −0
Original line number Diff line number Diff line
@@ -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");
    }
+46 −7
Original line number Diff line number Diff line
@@ -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 */,
@@ -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<>();

@@ -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());
    }
@@ -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
@@ -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.
        }