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
This commit is contained in:
Yan Yan
2022-12-01 02:09:09 +00:00
parent 923011ed09
commit 7c2f26f09a
5 changed files with 226 additions and 3 deletions

View File

@@ -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;

View File

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

View File

@@ -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 {

View File

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

View File

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