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

Commit 7c2f26f0 authored by Yan Yan's avatar Yan Yan
Browse files

Perform MOBIKE when data stall is suspected on VCN network

This commit updates VCN  to listen to the connectivity
diagnostics callback so that when there is a suspected data stall
VCN will call into IKE to perform MOBIKE to recover.

Bug: 261499808
Test: atest FrameworksVcnTests (new tests), CtsVcnTestCases
Change-Id: Ic8ec45870b7e5190db4fe6b8b7710f54e7fb61f4
parent 923011ed
Loading
Loading
Loading
Loading
+92 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR;
import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR;
@@ -36,6 +37,8 @@ import static com.android.server.VcnManagementService.VDBG;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.net.ConnectivityDiagnosticsManager;
import android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
import android.net.ConnectivityManager;
import android.net.InetAddresses;
import android.net.IpPrefix;
@@ -50,6 +53,7 @@ import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkProvider;
import android.net.NetworkRequest;
import android.net.NetworkScore;
import android.net.RouteInfo;
import android.net.TelephonyNetworkSpecifier;
@@ -546,6 +550,39 @@ public class VcnGatewayConnection extends StateMachine {
        }
    }

    /**
     * Sent when there is a suspected data stall on a network
     *
     * <p>Only relevant in the Connected state.
     *
     * @param arg1 The "all" token; this signal is always honored.
     * @param obj @NonNull An EventDataStallSuspectedInfo instance with relevant data.
     */
    private static final int EVENT_DATA_STALL_SUSPECTED = 13;

    private static class EventDataStallSuspectedInfo implements EventInfo {
        @NonNull public final Network network;

        EventDataStallSuspectedInfo(@NonNull Network network) {
            this.network = network;
        }

        @Override
        public int hashCode() {
            return Objects.hash(network);
        }

        @Override
        public boolean equals(@Nullable Object other) {
            if (!(other instanceof EventDataStallSuspectedInfo)) {
                return false;
            }

            final EventDataStallSuspectedInfo rhs = (EventDataStallSuspectedInfo) other;
            return Objects.equals(network, rhs.network);
        }
    }

    @VisibleForTesting(visibility = Visibility.PRIVATE)
    @NonNull
    final DisconnectedState mDisconnectedState = new DisconnectedState();
@@ -578,10 +615,13 @@ public class VcnGatewayConnection extends StateMachine {
    @NonNull
    private final VcnUnderlyingNetworkControllerCallback mUnderlyingNetworkControllerCallback;

    @NonNull private final VcnConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback;

    private final boolean mIsMobileDataEnabled;

    @NonNull private final IpSecManager mIpSecManager;
    @NonNull private final ConnectivityManager mConnectivityManager;
    @NonNull private final ConnectivityDiagnosticsManager mConnectivityDiagnosticsManager;

    @Nullable private IpSecTunnelInterface mTunnelIface = null;

@@ -748,6 +788,20 @@ public class VcnGatewayConnection extends StateMachine {
                        mUnderlyingNetworkControllerCallback);
        mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class);
        mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
        mConnectivityDiagnosticsManager =
                mVcnContext.getContext().getSystemService(ConnectivityDiagnosticsManager.class);

        mConnectivityDiagnosticsCallback = new VcnConnectivityDiagnosticsCallback();

        if (mConnectionConfig.hasGatewayOption(
                VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY)) {
            final NetworkRequest diagRequest =
                    new NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build();
            mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback(
                    diagRequest,
                    new HandlerExecutor(new Handler(vcnContext.getLooper())),
                    mConnectivityDiagnosticsCallback);
        }

        addState(mDisconnectedState);
        addState(mDisconnectingState);
@@ -810,6 +864,9 @@ public class VcnGatewayConnection extends StateMachine {
        mUnderlyingNetworkController.teardown();

        mGatewayStatusCallback.onQuit();

        mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
                mConnectivityDiagnosticsCallback);
    }

    /**
@@ -828,6 +885,20 @@ public class VcnGatewayConnection extends StateMachine {
        sendMessageAndAcquireWakeLock(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL);
    }

    private class VcnConnectivityDiagnosticsCallback extends ConnectivityDiagnosticsCallback {
        @Override
        public void onDataStallSuspected(ConnectivityDiagnosticsManager.DataStallReport report) {
            mVcnContext.ensureRunningOnLooperThread();

            final Network network = report.getNetwork();
            logInfo("Data stall suspected on " + network);
            sendMessageAndAcquireWakeLock(
                    EVENT_DATA_STALL_SUSPECTED,
                    TOKEN_ALL,
                    new EventDataStallSuspectedInfo(network));
        }
    }

    private class VcnUnderlyingNetworkControllerCallback
            implements UnderlyingNetworkControllerCallback {
        @Override
@@ -1367,7 +1438,8 @@ public class VcnGatewayConnection extends StateMachine {
                case EVENT_SUBSCRIPTIONS_CHANGED: // Fallthrough
                case EVENT_SAFE_MODE_TIMEOUT_EXCEEDED: // Fallthrough
                case EVENT_MIGRATION_COMPLETED: // Fallthrough
                case EVENT_IKE_CONNECTION_INFO_CHANGED:
                case EVENT_IKE_CONNECTION_INFO_CHANGED: // Fallthrough
                case EVENT_DATA_STALL_SUSPECTED:
                    logUnexpectedEvent(msg.what);
                    break;
                default:
@@ -1925,6 +1997,11 @@ public class VcnGatewayConnection extends StateMachine {
                    mIkeConnectionInfo =
                            ((EventIkeConnectionInfoChangedInfo) msg.obj).ikeConnectionInfo;
                    break;
                case EVENT_DATA_STALL_SUSPECTED:
                    final Network networkWithDataStall =
                            ((EventDataStallSuspectedInfo) msg.obj).network;
                    handleDataStallSuspected(networkWithDataStall);
                    break;
                default:
                    logUnhandledMessage(msg);
                    break;
@@ -1985,6 +2062,15 @@ public class VcnGatewayConnection extends StateMachine {
            }
        }

        private void handleDataStallSuspected(Network networkWithDataStall) {
            if (mUnderlying != null
                    && mNetworkAgent != null
                    && mNetworkAgent.getNetwork().equals(networkWithDataStall)) {
                logInfo("Perform Mobility update to recover from suspected data stall");
                mIkeSession.setNetwork(mUnderlying.network);
            }
        }

        protected void setupInterfaceAndNetworkAgent(
                int token,
                @NonNull IpSecTunnelInterface tunnelIface,
@@ -2423,6 +2509,11 @@ public class VcnGatewayConnection extends StateMachine {
        return mUnderlyingNetworkControllerCallback;
    }

    @VisibleForTesting(visibility = Visibility.PRIVATE)
    ConnectivityDiagnosticsCallback getConnectivityDiagnosticsCallback() {
        return mConnectivityDiagnosticsCallback;
    }

    @VisibleForTesting(visibility = Visibility.PRIVATE)
    UnderlyingNetworkRecord getUnderlyingNetwork() {
        return mUnderlying;
+2 −1
Original line number Diff line number Diff line
@@ -149,7 +149,8 @@ public class VcnGatewayConnectionConfigTest {
        return buildTestConfigWithExposedCapsAndOptions(builder, gatewayOptions, EXPOSED_CAPS);
    }

    private static VcnGatewayConnectionConfig buildTestConfigWithGatewayOptions(
    // Public for use in VcnGatewayConnectionTest
    public static VcnGatewayConnectionConfig buildTestConfigWithGatewayOptions(
            Set<Integer> gatewayOptions) {
        return buildTestConfigWithExposedCapsAndOptions(newBuilder(), gatewayOptions, EXPOSED_CAPS);
    }
+58 −0
Original line number Diff line number Diff line
@@ -50,9 +50,11 @@ import static org.mockito.Mockito.when;

import static java.util.Collections.singletonList;

import android.net.ConnectivityDiagnosticsManager.DataStallReport;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.ipsec.ike.ChildSaProposal;
@@ -63,10 +65,12 @@ import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnGatewayConnectionConfigTest;
import android.net.vcn.VcnManager.VcnErrorCode;
import android.os.PersistableBundle;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
import com.android.server.vcn.util.MtuUtils;

import org.junit.Before;
@@ -88,6 +92,7 @@ import java.util.function.Consumer;
public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase {
    private VcnIkeSession mIkeSession;
    private VcnNetworkAgent mNetworkAgent;
    private Network mVcnNetwork;

    @Before
    public void setUp() throws Exception {
@@ -98,6 +103,9 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
                .when(mDeps)
                .newNetworkAgent(any(), any(), any(), any(), any(), any(), any(), any(), any());

        mVcnNetwork = mock(Network.class);
        doReturn(mVcnNetwork).when(mNetworkAgent).getNetwork();

        mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1);

        mIkeSession = mGatewayConnection.buildIkeSession(TEST_UNDERLYING_NETWORK_RECORD_1.network);
@@ -166,6 +174,56 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection
        assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());
    }

    private void verifyDataStallTriggersMigration(
            UnderlyingNetworkRecord networkRecord,
            Network networkWithDataStall,
            boolean expectMobilityUpdate)
            throws Exception {
        mGatewayConnection.setUnderlyingNetwork(networkRecord);
        triggerChildOpened();
        mTestLooper.dispatchAll();

        final DataStallReport report =
                new DataStallReport(
                        networkWithDataStall,
                        1234 /* reportTimestamp */,
                        1 /* detectionMethod */,
                        new LinkProperties(),
                        new NetworkCapabilities(),
                        new PersistableBundle());

        mGatewayConnection.getConnectivityDiagnosticsCallback().onDataStallSuspected(report);
        mTestLooper.dispatchAll();

        assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState());

        if (expectMobilityUpdate) {
            verify(mIkeSession).setNetwork(networkRecord.network);
        } else {
            verify(mIkeSession, never()).setNetwork(any(Network.class));
        }
    }

    @Test
    public void testDataStallTriggersMigration() throws Exception {
        verifyDataStallTriggersMigration(
                TEST_UNDERLYING_NETWORK_RECORD_1, mVcnNetwork, true /* expectMobilityUpdate */);
    }

    @Test
    public void testDataStallWontTriggerMigrationWhenOnOtherNetwork() throws Exception {
        verifyDataStallTriggersMigration(
                TEST_UNDERLYING_NETWORK_RECORD_1,
                mock(Network.class),
                false /* expectMobilityUpdate */);
    }

    @Test
    public void testDataStallWontTriggerMigrationWhenUnderlyingNetworkLost() throws Exception {
        verifyDataStallTriggersMigration(
                null /* networkRecord */, mock(Network.class), false /* expectMobilityUpdate */);
    }

    private void verifyVcnTransformsApplied(
            VcnGatewayConnection vcnGatewayConnection, boolean expectForwardTransform)
            throws Exception {
+65 −1
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY;

import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR;
import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration;
@@ -34,20 +35,25 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Matchers.eq;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
import android.net.IpSecManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.TelephonyNetworkSpecifier;
import android.net.vcn.VcnGatewayConnectionConfig;
import android.net.vcn.VcnGatewayConnectionConfigTest;
import android.net.vcn.VcnTransportInfo;
import android.net.wifi.WifiInfo;
@@ -64,6 +70,7 @@ import com.android.server.vcn.routeselection.UnderlyingNetworkRecord;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;

import java.net.InetAddress;
import java.util.Arrays;
@@ -71,7 +78,9 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executor;

/** Tests for TelephonySubscriptionTracker */
@RunWith(AndroidJUnit4.class)
@@ -287,5 +296,60 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase {
        verify(vcnNetworkAgent).unregister();

        verifyWakeLockReleased();

        verify(mConnDiagMgr)
                .unregisterConnectivityDiagnosticsCallback(
                        mGatewayConnection.getConnectivityDiagnosticsCallback());
    }

    private VcnGatewayConnection buildConnectionWithDataStallHandling(
            boolean datatStallHandlingEnabled) throws Exception {
        Set<Integer> options =
                datatStallHandlingEnabled
                        ? Collections.singleton(
                                VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY)
                        : Collections.emptySet();
        final VcnGatewayConnectionConfig gatewayConfig =
                VcnGatewayConnectionConfigTest.buildTestConfigWithGatewayOptions(options);
        final VcnGatewayConnection gatewayConnection =
                new VcnGatewayConnection(
                        mVcnContext,
                        TEST_SUB_GRP,
                        TEST_SUBSCRIPTION_SNAPSHOT,
                        gatewayConfig,
                        mGatewayStatusCallback,
                        true /* isMobileDataEnabled */,
                        mDeps);
        return gatewayConnection;
    }

    @Test
    public void testDataStallHandlingEnabled() throws Exception {
        final VcnGatewayConnection gatewayConnection =
                buildConnectionWithDataStallHandling(true /* datatStallHandlingEnabled */);

        final ArgumentCaptor<NetworkRequest> networkRequestCaptor =
                ArgumentCaptor.forClass(NetworkRequest.class);
        verify(mConnDiagMgr)
                .registerConnectivityDiagnosticsCallback(
                        networkRequestCaptor.capture(),
                        any(Executor.class),
                        eq(gatewayConnection.getConnectivityDiagnosticsCallback()));

        final NetworkRequest nr = networkRequestCaptor.getValue();
        final NetworkRequest expected =
                new NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build();
        assertEquals(expected, nr);
    }

    @Test
    public void testDataStallHandlingDisabled() throws Exception {
        buildConnectionWithDataStallHandling(false /* datatStallHandlingEnabled */);

        verify(mConnDiagMgr, never())
                .registerConnectivityDiagnosticsCallback(
                        any(NetworkRequest.class),
                        any(Executor.class),
                        any(ConnectivityDiagnosticsCallback.class));
    }
}
+9 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;

import android.annotation.NonNull;
import android.content.Context;
import android.net.ConnectivityDiagnosticsManager;
import android.net.ConnectivityManager;
import android.net.InetAddresses;
import android.net.IpSecConfig;
@@ -157,6 +158,7 @@ public class VcnGatewayConnectionTestBase {

    @NonNull protected final IpSecService mIpSecSvc;
    @NonNull protected final ConnectivityManager mConnMgr;
    @NonNull protected final ConnectivityDiagnosticsManager mConnDiagMgr;

    @NonNull protected final IkeSessionConnectionInfo mIkeConnectionInfo;
    @NonNull protected final IkeSessionConfiguration mIkeSessionConfiguration;
@@ -186,6 +188,13 @@ public class VcnGatewayConnectionTestBase {
        VcnTestUtils.setupSystemService(
                mContext, mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);

        mConnDiagMgr = mock(ConnectivityDiagnosticsManager.class);
        VcnTestUtils.setupSystemService(
                mContext,
                mConnDiagMgr,
                Context.CONNECTIVITY_DIAGNOSTICS_SERVICE,
                ConnectivityDiagnosticsManager.class);

        mIkeConnectionInfo =
                new IkeSessionConnectionInfo(TEST_ADDR, TEST_ADDR_2, mock(Network.class));
        mIkeSessionConfiguration = new IkeSessionConfiguration.Builder(mIkeConnectionInfo).build();