From 923011ed0974f0b8e213b77d7103c79e90be52a7 Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Tue, 6 Dec 2022 02:29:37 +0000 Subject: Add VCN gateway option to enable data stall handling This commit allows caller to enable VCN's ability of handling data stall by setting a VCN gateway option. Bug: 261499808 Test: atest FrameworksVcnTests (new tests), CtsVcnTestCases Change-Id: If37b26faf851153d64face66d10201d7eecad685 --- .../net/vcn/VcnGatewayConnectionConfig.java | 117 ++++++++++++++++++++- .../net/vcn/VcnGatewayConnectionConfigTest.java | 87 ++++++++++++++- 2 files changed, 198 insertions(+), 6 deletions(-) diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index 2339656979b5..7718053ddf7d 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -42,6 +42,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -130,6 +131,33 @@ public final class VcnGatewayConnectionConfig { }) public @interface VcnSupportedCapability {} + /** + * Perform mobility update to attempt recovery from suspected data stalls. + * + *

If set, the gatway connection will monitor the data stall detection of the VCN network. + * When there is a suspected data stall, the gateway connection will attempt recovery by + * performing a mobility update on the underlying IKE session. + * + * @hide + */ + public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = {"VCN_GATEWAY_OPTION_"}, + value = { + // TODO: b/261499808 Add VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY + // when it is exposed + }) + public @interface VcnGatewayOption {} + + private static final Set ALLOWED_GATEWAY_OPTIONS = new ArraySet<>(); + + static { + ALLOWED_GATEWAY_OPTIONS.add(VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY); + } + private static final int DEFAULT_MAX_MTU = 1500; /** @@ -201,6 +229,9 @@ public final class VcnGatewayConnectionConfig { private static final String RETRY_INTERVAL_MS_KEY = "mRetryIntervalsMs"; @NonNull private final long[] mRetryIntervalsMs; + private static final String GATEWAY_OPTIONS_KEY = "mGatewayOptions"; + @NonNull private final Set mGatewayOptions; + /** Builds a VcnGatewayConnectionConfig with the specified parameters. */ private VcnGatewayConnectionConfig( @NonNull String gatewayConnectionName, @@ -208,12 +239,14 @@ public final class VcnGatewayConnectionConfig { @NonNull Set exposedCapabilities, @NonNull List underlyingNetworkTemplates, @NonNull long[] retryIntervalsMs, - @IntRange(from = MIN_MTU_V6) int maxMtu) { + @IntRange(from = MIN_MTU_V6) int maxMtu, + @NonNull Set gatewayOptions) { mGatewayConnectionName = gatewayConnectionName; mTunnelConnectionParams = tunnelConnectionParams; mExposedCapabilities = new TreeSet(exposedCapabilities); mRetryIntervalsMs = retryIntervalsMs; mMaxMtu = maxMtu; + mGatewayOptions = Collections.unmodifiableSet(new HashSet(gatewayOptions)); mUnderlyingNetworkTemplates = new ArrayList<>(underlyingNetworkTemplates); if (mUnderlyingNetworkTemplates.isEmpty()) { @@ -256,6 +289,20 @@ public final class VcnGatewayConnectionConfig { VcnUnderlyingNetworkTemplate::fromPersistableBundle); } + final PersistableBundle gatewayOptionsBundle = in.getPersistableBundle(GATEWAY_OPTIONS_KEY); + + if (gatewayOptionsBundle == null) { + // GATEWAY_OPTIONS_KEY was added in Android U. Thus VcnGatewayConnectionConfig created + // on old platforms will not have this data and will be assigned with the default value + mGatewayOptions = Collections.emptySet(); + } else { + mGatewayOptions = + new HashSet<>( + PersistableBundleUtils.toList( + gatewayOptionsBundle, + PersistableBundleUtils.INTEGER_DESERIALIZER)); + } + mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY); mMaxMtu = in.getInt(MAX_MTU_KEY); @@ -279,6 +326,10 @@ public final class VcnGatewayConnectionConfig { Preconditions.checkArgument( mMaxMtu >= MIN_MTU_V6, "maxMtu must be at least IPv6 min MTU (1280)"); + + for (int option : mGatewayOptions) { + validateGatewayOption(option); + } } private static void checkValidCapability(int capability) { @@ -315,6 +366,12 @@ public final class VcnGatewayConnectionConfig { } } + private static void validateGatewayOption(int option) { + if (!ALLOWED_GATEWAY_OPTIONS.contains(option)) { + throw new IllegalArgumentException("Invalid vcn gateway option: " + option); + } + } + /** * Returns the configured Gateway Connection name. * @@ -398,6 +455,18 @@ public final class VcnGatewayConnectionConfig { return mMaxMtu; } + /** + * Checks if the given VCN gateway option is enabled. + * + * @param option the option to check. + * @throws IllegalArgumentException if the provided option is invalid. + * @hide + */ + public boolean hasGatewayOption(@VcnGatewayOption int option) { + validateGatewayOption(option); + return mGatewayOptions.contains(option); + } + /** * Converts this config to a PersistableBundle. * @@ -418,11 +487,16 @@ public final class VcnGatewayConnectionConfig { PersistableBundleUtils.fromList( mUnderlyingNetworkTemplates, VcnUnderlyingNetworkTemplate::toPersistableBundle); + final PersistableBundle gatewayOptionsBundle = + PersistableBundleUtils.fromList( + new ArrayList<>(mGatewayOptions), + PersistableBundleUtils.INTEGER_SERIALIZER); result.putString(GATEWAY_CONNECTION_NAME_KEY, mGatewayConnectionName); result.putPersistableBundle(TUNNEL_CONNECTION_PARAMS_KEY, tunnelConnectionParamsBundle); result.putPersistableBundle(EXPOSED_CAPABILITIES_KEY, exposedCapsBundle); result.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, networkTemplatesBundle); + result.putPersistableBundle(GATEWAY_OPTIONS_KEY, gatewayOptionsBundle); result.putLongArray(RETRY_INTERVAL_MS_KEY, mRetryIntervalsMs); result.putInt(MAX_MTU_KEY, mMaxMtu); @@ -437,7 +511,8 @@ public final class VcnGatewayConnectionConfig { mExposedCapabilities, mUnderlyingNetworkTemplates, Arrays.hashCode(mRetryIntervalsMs), - mMaxMtu); + mMaxMtu, + mGatewayOptions); } @Override @@ -452,7 +527,8 @@ public final class VcnGatewayConnectionConfig { && mExposedCapabilities.equals(rhs.mExposedCapabilities) && mUnderlyingNetworkTemplates.equals(rhs.mUnderlyingNetworkTemplates) && Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs) - && mMaxMtu == rhs.mMaxMtu; + && mMaxMtu == rhs.mMaxMtu + && mGatewayOptions.equals(rhs.mGatewayOptions); } /** @@ -470,6 +546,8 @@ public final class VcnGatewayConnectionConfig { @NonNull private long[] mRetryIntervalsMs = DEFAULT_RETRY_INTERVALS_MS; private int mMaxMtu = DEFAULT_MAX_MTU; + @NonNull private final Set mGatewayOptions = new ArraySet<>(); + // TODO: (b/175829816) Consider VCN-exposed capabilities that may be transport dependent. // Consider the case where the VCN might only expose MMS on WiFi, but defer to MMS // when on Cell. @@ -627,6 +705,36 @@ public final class VcnGatewayConnectionConfig { return this; } + /** + * Enables the specified VCN gateway option. + * + * @param option the option to be enabled + * @return this {@link Builder} instance, for chaining + * @throws IllegalArgumentException if the provided option is invalid + * @hide + */ + @NonNull + public Builder addGatewayOption(@VcnGatewayOption int option) { + validateGatewayOption(option); + mGatewayOptions.add(option); + return this; + } + + /** + * Resets (disables) the specified VCN gateway option. + * + * @param option the option to be disabled + * @return this {@link Builder} instance, for chaining + * @throws IllegalArgumentException if the provided option is invalid + * @hide + */ + @NonNull + public Builder removeGatewayOption(@VcnGatewayOption int option) { + validateGatewayOption(option); + mGatewayOptions.remove(option); + return this; + } + /** * Builds and validates the VcnGatewayConnectionConfig. * @@ -640,7 +748,8 @@ public final class VcnGatewayConnectionConfig { mExposedCapabilities, mUnderlyingNetworkTemplates, mRetryIntervalsMs, - mMaxMtu); + mMaxMtu, + mGatewayOptions); } } } diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java index 2aef9ae7ca32..de846579071a 100644 --- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java @@ -19,9 +19,11 @@ package android.net.vcn; import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE; import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES; import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_TEMPLATES_KEY; +import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; @@ -42,7 +44,9 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @@ -79,6 +83,9 @@ public class VcnGatewayConnectionConfigTest { }; public static final int MAX_MTU = 1360; + private static final Set GATEWAY_OPTIONS = + Collections.singleton(VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY); + public static final IkeTunnelConnectionParams TUNNEL_CONNECTION_PARAMS = TunnelConnectionParamsUtilsTest.buildTestParams(); @@ -109,10 +116,16 @@ public class VcnGatewayConnectionConfigTest { TUNNEL_CONNECTION_PARAMS); } - private static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps( - VcnGatewayConnectionConfig.Builder builder, int... exposedCaps) { + private static VcnGatewayConnectionConfig buildTestConfigWithExposedCapsAndOptions( + VcnGatewayConnectionConfig.Builder builder, + Set gatewayOptions, + int... exposedCaps) { builder.setRetryIntervalsMillis(RETRY_INTERVALS_MS).setMaxMtu(MAX_MTU); + for (int option : gatewayOptions) { + builder.addGatewayOption(option); + } + for (int caps : exposedCaps) { builder.addExposedCapability(caps); } @@ -120,11 +133,27 @@ public class VcnGatewayConnectionConfigTest { return builder.build(); } + private static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps( + VcnGatewayConnectionConfig.Builder builder, int... exposedCaps) { + return buildTestConfigWithExposedCapsAndOptions( + builder, Collections.emptySet(), exposedCaps); + } + // Public for use in VcnGatewayConnectionTest public static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(int... exposedCaps) { return buildTestConfigWithExposedCaps(newBuilder(), exposedCaps); } + private static VcnGatewayConnectionConfig buildTestConfigWithGatewayOptions( + VcnGatewayConnectionConfig.Builder builder, Set gatewayOptions) { + return buildTestConfigWithExposedCapsAndOptions(builder, gatewayOptions, EXPOSED_CAPS); + } + + private static VcnGatewayConnectionConfig buildTestConfigWithGatewayOptions( + Set gatewayOptions) { + return buildTestConfigWithExposedCapsAndOptions(newBuilder(), gatewayOptions, EXPOSED_CAPS); + } + @Test public void testBuilderRequiresNonNullGatewayConnectionName() { try { @@ -210,6 +239,15 @@ public class VcnGatewayConnectionConfigTest { } } + @Test + public void testBuilderRequiresValidOption() { + try { + newBuilder().addGatewayOption(-1); + fail("Expected exception due to the invalid VCN gateway option"); + } catch (IllegalArgumentException e) { + } + } + @Test public void testBuilderAndGetters() { final VcnGatewayConnectionConfig config = buildTestConfig(); @@ -225,6 +263,20 @@ public class VcnGatewayConnectionConfigTest { assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis()); assertEquals(MAX_MTU, config.getMaxMtu()); + + assertFalse( + config.hasGatewayOption( + VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY)); + } + + @Test + public void testBuilderAndGettersWithOptions() { + final VcnGatewayConnectionConfig config = + buildTestConfigWithGatewayOptions(GATEWAY_OPTIONS); + + for (int option : GATEWAY_OPTIONS) { + assertTrue(config.hasGatewayOption(option)); + } } @Test @@ -234,6 +286,14 @@ public class VcnGatewayConnectionConfigTest { assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle())); } + @Test + public void testPersistableBundleWithOptions() { + final VcnGatewayConnectionConfig config = + buildTestConfigWithGatewayOptions(GATEWAY_OPTIONS); + + assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle())); + } + @Test public void testParsePersistableBundleWithoutVcnUnderlyingNetworkTemplates() { PersistableBundle configBundle = buildTestConfig().toPersistableBundle(); @@ -318,4 +378,27 @@ public class VcnGatewayConnectionConfigTest { assertNotEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesNotEqual); assertNotEquals(config, configNotEqual); } + + private static VcnGatewayConnectionConfig buildConfigWithGatewayOptionsForEqualityTest( + Set gatewayOptions) { + return buildTestConfigWithGatewayOptions( + new VcnGatewayConnectionConfig.Builder( + "buildConfigWithGatewayOptionsForEqualityTest", TUNNEL_CONNECTION_PARAMS), + gatewayOptions); + } + + @Test + public void testVcnGatewayOptionsEquality() throws Exception { + final VcnGatewayConnectionConfig config = + buildConfigWithGatewayOptionsForEqualityTest(GATEWAY_OPTIONS); + + final VcnGatewayConnectionConfig configEqual = + buildConfigWithGatewayOptionsForEqualityTest(GATEWAY_OPTIONS); + + final VcnGatewayConnectionConfig configNotEqual = + buildConfigWithGatewayOptionsForEqualityTest(Collections.emptySet()); + + assertEquals(config, configEqual); + assertNotEquals(config, configNotEqual); + } } -- cgit v1.2.3-59-g8ed1b From 7c2f26f09aecb7e800969ce510408e9eb126a3fe Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Thu, 1 Dec 2022 02:09:09 +0000 Subject: 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 --- .../android/server/vcn/VcnGatewayConnection.java | 93 +++++++++++++++++++++- .../net/vcn/VcnGatewayConnectionConfigTest.java | 3 +- .../VcnGatewayConnectionConnectedStateTest.java | 58 ++++++++++++++ .../server/vcn/VcnGatewayConnectionTest.java | 66 ++++++++++++++- .../server/vcn/VcnGatewayConnectionTestBase.java | 9 +++ 5 files changed, 226 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 05df22f124ed..3be16a1fec44 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -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 + * + *

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; diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java index de846579071a..40408880a2c6 100644 --- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java @@ -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 gatewayOptions) { return buildTestConfigWithExposedCapsAndOptions(newBuilder(), gatewayOptions, EXPOSED_CAPS); } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index 15d4f1097108..1c21a067bde8 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -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 { diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java index 6a9a1e22cab1..a4ee2de9f433 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java @@ -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 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 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)); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index 785bff167ad2..7bafd243799f 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -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(); -- cgit v1.2.3-59-g8ed1b From 9da3bf1fbc1bbab79821cd572857372df27af1cb Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Mon, 12 Dec 2022 23:38:39 +0000 Subject: APIs to enable data stall handling in VCN This commit expose APIs to set VCN Gateway Options for additional VCN abilities, and the option to allow VCN to detect suspected data stall and recover with IKE mobility update. Bug: 261499808 Test: make update-api && make API-Coverage-Bug: 262297702 Change-Id: If8097d587e7099e9bb9a43328dad3cac3cfb9703 --- core/api/current.txt | 4 ++++ core/java/android/net/vcn/VcnGatewayConnectionConfig.java | 10 +++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index 2ba2f5bd2c85..ca7025e160ab 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -27301,13 +27301,17 @@ package android.net.vcn { method @IntRange(from=0x500) public int getMaxMtu(); method @NonNull public long[] getRetryIntervalsMillis(); method @NonNull public java.util.List getVcnUnderlyingNetworkPriorities(); + method public boolean hasGatewayOption(int); + field public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; // 0x0 } public static final class VcnGatewayConnectionConfig.Builder { ctor public VcnGatewayConnectionConfig.Builder(@NonNull String, @NonNull android.net.ipsec.ike.IkeTunnelConnectionParams); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int); + method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addGatewayOption(int); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build(); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int); + method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeGatewayOption(int); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List); diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index 7718053ddf7d..b8850f427cfc 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -137,8 +137,6 @@ public final class VcnGatewayConnectionConfig { *

If set, the gatway connection will monitor the data stall detection of the VCN network. * When there is a suspected data stall, the gateway connection will attempt recovery by * performing a mobility update on the underlying IKE session. - * - * @hide */ public static final int VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY = 0; @@ -147,8 +145,7 @@ public final class VcnGatewayConnectionConfig { @IntDef( prefix = {"VCN_GATEWAY_OPTION_"}, value = { - // TODO: b/261499808 Add VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY - // when it is exposed + VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY, }) public @interface VcnGatewayOption {} @@ -460,7 +457,8 @@ public final class VcnGatewayConnectionConfig { * * @param option the option to check. * @throws IllegalArgumentException if the provided option is invalid. - * @hide + * @see Builder#addGatewayOption(int) + * @see Builder#removeGatewayOption(int) */ public boolean hasGatewayOption(@VcnGatewayOption int option) { validateGatewayOption(option); @@ -711,7 +709,6 @@ public final class VcnGatewayConnectionConfig { * @param option the option to be enabled * @return this {@link Builder} instance, for chaining * @throws IllegalArgumentException if the provided option is invalid - * @hide */ @NonNull public Builder addGatewayOption(@VcnGatewayOption int option) { @@ -726,7 +723,6 @@ public final class VcnGatewayConnectionConfig { * @param option the option to be disabled * @return this {@link Builder} instance, for chaining * @throws IllegalArgumentException if the provided option is invalid - * @hide */ @NonNull public Builder removeGatewayOption(@VcnGatewayOption int option) { -- cgit v1.2.3-59-g8ed1b