diff options
6 files changed, 328 insertions, 19 deletions
diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java index e1feb5aab869..6427ae2dc13c 100644 --- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java +++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java @@ -24,6 +24,9 @@ import android.net.NetworkCapabilities; import android.os.Handler; import android.os.ParcelUuid; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; + import java.util.Objects; /** @@ -72,7 +75,8 @@ public class UnderlyingNetworkTracker extends Handler { @NonNull public final LinkProperties linkProperties; public final boolean blocked; - private UnderlyingNetworkRecord( + @VisibleForTesting(visibility = Visibility.PRIVATE) + UnderlyingNetworkRecord( @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities, @NonNull LinkProperties linkProperties, diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 4e0c0c54923b..93cf470c3b13 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -360,11 +360,25 @@ public class VcnGatewayConnection extends StateMachine { */ private static final int EVENT_TEARDOWN_TIMEOUT_EXPIRED = 8; - @NonNull private final DisconnectedState mDisconnectedState = new DisconnectedState(); - @NonNull private final DisconnectingState mDisconnectingState = new DisconnectingState(); - @NonNull private final ConnectingState mConnectingState = new ConnectingState(); - @NonNull private final ConnectedState mConnectedState = new ConnectedState(); - @NonNull private final RetryTimeoutState mRetryTimeoutState = new RetryTimeoutState(); + @VisibleForTesting(visibility = Visibility.PRIVATE) + @NonNull + final DisconnectedState mDisconnectedState = new DisconnectedState(); + + @VisibleForTesting(visibility = Visibility.PRIVATE) + @NonNull + final DisconnectingState mDisconnectingState = new DisconnectingState(); + + @VisibleForTesting(visibility = Visibility.PRIVATE) + @NonNull + final ConnectingState mConnectingState = new ConnectingState(); + + @VisibleForTesting(visibility = Visibility.PRIVATE) + @NonNull + final ConnectedState mConnectedState = new ConnectedState(); + + @VisibleForTesting(visibility = Visibility.PRIVATE) + @NonNull + final RetryTimeoutState mRetryTimeoutState = new RetryTimeoutState(); @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; @@ -455,7 +469,8 @@ public class VcnGatewayConnection extends StateMachine { this(vcnContext, subscriptionGroup, connectionConfig, new Dependencies()); } - private VcnGatewayConnection( + @VisibleForTesting(visibility = Visibility.PRIVATE) + VcnGatewayConnection( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull VcnGatewayConnectionConfig connectionConfig, @@ -508,7 +523,6 @@ public class VcnGatewayConnection extends StateMachine { EVENT_DISCONNECT_REQUESTED, TOKEN_ALL, new EventDisconnectRequestedInfo(DISCONNECT_REASON_TEARDOWN)); - quit(); // TODO: Notify VcnInstance (via callbacks) of permanent teardown of this tunnel, since this // is also called asynchronously when a NetworkAgent becomes unwanted @@ -667,6 +681,8 @@ public class VcnGatewayConnection extends StateMachine { protected void handleDisconnectRequested(String msg) { Slog.v(TAG, "Tearing down. Cause: " + msg); + mIsRunning = false; + teardownNetwork(); teardownIke(); @@ -697,7 +713,37 @@ public class VcnGatewayConnection extends StateMachine { */ private class DisconnectedState extends BaseState { @Override - protected void processStateMsg(Message msg) {} + protected void enterState() { + if (!mIsRunning) { + quitNow(); // Ignore all queued events; cleanup is complete. + } + + if (mIkeSession != null || mNetworkAgent != null) { + Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState"); + } + } + + @Override + protected void processStateMsg(Message msg) { + switch (msg.what) { + case EVENT_UNDERLYING_NETWORK_CHANGED: + // First network found; start tunnel + mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying; + + if (mUnderlying != null) { + transitionTo(mConnectingState); + } + break; + case EVENT_DISCONNECT_REQUESTED: + mIsRunning = false; + + quitNow(); + break; + default: + logUnhandledMessage(msg); + break; + } + } } private abstract class ActiveBaseState extends BaseState { @@ -893,7 +939,32 @@ public class VcnGatewayConnection extends StateMachine { } } - /** External dependencies used by VcnGatewayConnection, for injection in tests. */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + UnderlyingNetworkTrackerCallback getUnderlyingNetworkTrackerCallback() { + return mUnderlyingNetworkTrackerCallback; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + UnderlyingNetworkRecord getUnderlyingNetwork() { + return mUnderlying; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + void setUnderlyingNetwork(@Nullable UnderlyingNetworkRecord record) { + mUnderlying = record; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + boolean isRunning() { + return mIsRunning; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + void setIsRunning(boolean isRunning) { + mIsRunning = isRunning; + } + + /** External dependencies used by VcnGatewayConnection, for injection in tests */ @VisibleForTesting(visibility = Visibility.PRIVATE) public static class Dependencies { /** Builds a new UnderlyingNetworkTracker. */ diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 696110f01869..131d9c3aed1b 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -18,6 +18,7 @@ package com.android.server; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; +import static com.android.server.vcn.VcnTestUtils.setupSystemService; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -127,11 +128,16 @@ public class VcnManagementServiceTest { private final VcnManagementService mVcnMgmtSvc; public VcnManagementServiceTest() throws Exception { - setupSystemService(mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); - setupSystemService(mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class); setupSystemService( - mSubMgr, Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class); - setupSystemService(mAppOpsMgr, Context.APP_OPS_SERVICE, AppOpsManager.class); + mMockContext, mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); + setupSystemService( + mMockContext, mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class); + setupSystemService( + mMockContext, + mSubMgr, + Context.TELEPHONY_SUBSCRIPTION_SERVICE, + SubscriptionManager.class); + setupSystemService(mMockContext, mAppOpsMgr, Context.APP_OPS_SERVICE, AppOpsManager.class); doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName(); @@ -173,11 +179,6 @@ public class VcnManagementServiceTest { mTestLooper.dispatchAll(); } - private void setupSystemService(Object service, String name, Class<?> serviceClass) { - doReturn(name).when(mMockContext).getSystemServiceName(serviceClass); - doReturn(service).when(mMockContext).getSystemService(name); - } - private void setupMockedCarrierPrivilege(boolean isPrivileged) { doReturn(Collections.singletonList(TEST_SUBSCRIPTION_INFO)) .when(mSubMgr) diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java new file mode 100644 index 000000000000..4ecd21503165 --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for VcnGatewayConnection.DisconnectedState */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnectionTestBase { + @Before + public void setUp() throws Exception { + super.setUp(); + + mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState); + mTestLooper.dispatchAll(); + } + + @Test + public void testEnterWhileNotRunningTriggersQuit() throws Exception { + final VcnGatewayConnection vgc = + new VcnGatewayConnection(mVcnContext, TEST_SUB_GRP, mConfig, mDeps); + + vgc.setIsRunning(false); + vgc.transitionTo(vgc.mDisconnectedState); + mTestLooper.dispatchAll(); + + assertNull(vgc.getCurrentState()); + } + + @Test + public void testNetworkChangesTriggerStateTransitions() throws Exception { + mGatewayConnection + .getUnderlyingNetworkTrackerCallback() + .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState()); + } + + @Test + public void testNullNetworkDoesNotTriggerStateTransition() throws Exception { + mGatewayConnection + .getUnderlyingNetworkTrackerCallback() + .onSelectedUnderlyingNetworkChanged(null); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); + } + + @Test + public void testTeardown() throws Exception { + mGatewayConnection.teardownAsynchronously(); + mTestLooper.dispatchAll(); + + assertNull(mGatewayConnection.getCurrentState()); + verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any()); + } +} diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java new file mode 100644 index 000000000000..1725dd983115 --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn; + +import static com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; +import static com.android.server.vcn.VcnTestUtils.setupIpSecManager; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import android.annotation.NonNull; +import android.content.Context; +import android.net.IpSecManager; +import android.net.IpSecTunnelInterfaceResponse; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.vcn.VcnGatewayConnectionConfig; +import android.net.vcn.VcnGatewayConnectionConfigTest; +import android.os.ParcelUuid; +import android.os.test.TestLooper; + +import com.android.server.IpSecService; + +import org.junit.Before; + +import java.util.UUID; + +public class VcnGatewayConnectionTestBase { + protected static final ParcelUuid TEST_SUB_GRP = new ParcelUuid(UUID.randomUUID()); + protected static final int TEST_IPSEC_TUNNEL_RESOURCE_ID = 1; + protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE"; + protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 = + new UnderlyingNetworkRecord( + new Network(0), + new NetworkCapabilities(), + new LinkProperties(), + false /* blocked */); + protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_2 = + new UnderlyingNetworkRecord( + new Network(1), + new NetworkCapabilities(), + new LinkProperties(), + false /* blocked */); + + @NonNull protected final Context mContext; + @NonNull protected final TestLooper mTestLooper; + @NonNull protected final VcnNetworkProvider mVcnNetworkProvider; + @NonNull protected final VcnContext mVcnContext; + @NonNull protected final VcnGatewayConnectionConfig mConfig; + @NonNull protected final VcnGatewayConnection.Dependencies mDeps; + @NonNull protected final UnderlyingNetworkTracker mUnderlyingNetworkTracker; + + @NonNull protected final IpSecService mIpSecSvc; + + protected VcnGatewayConnection mGatewayConnection; + + public VcnGatewayConnectionTestBase() { + mContext = mock(Context.class); + mTestLooper = new TestLooper(); + mVcnNetworkProvider = mock(VcnNetworkProvider.class); + mVcnContext = mock(VcnContext.class); + mConfig = VcnGatewayConnectionConfigTest.buildTestConfig(); + mDeps = mock(VcnGatewayConnection.Dependencies.class); + mUnderlyingNetworkTracker = mock(UnderlyingNetworkTracker.class); + + mIpSecSvc = mock(IpSecService.class); + setupIpSecManager(mContext, mIpSecSvc); + + doReturn(mContext).when(mVcnContext).getContext(); + doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper(); + doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider(); + + doReturn(mUnderlyingNetworkTracker) + .when(mDeps) + .newUnderlyingNetworkTracker(any(), any(), any()); + } + + @Before + public void setUp() throws Exception { + IpSecTunnelInterfaceResponse resp = + new IpSecTunnelInterfaceResponse( + IpSecManager.Status.OK, + TEST_IPSEC_TUNNEL_RESOURCE_ID, + TEST_IPSEC_TUNNEL_IFACE); + doReturn(resp).when(mIpSecSvc).createTunnelInterface(any(), any(), any(), any(), any()); + + mGatewayConnection = new VcnGatewayConnection(mVcnContext, TEST_SUB_GRP, mConfig, mDeps); + } +} diff --git a/tests/vcn/java/com/android/server/vcn/VcnTestUtils.java b/tests/vcn/java/com/android/server/vcn/VcnTestUtils.java new file mode 100644 index 000000000000..2b1080650d6d --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/VcnTestUtils.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn; + +import static org.mockito.Mockito.doReturn; + +import android.content.Context; +import android.net.IpSecManager; + +import com.android.server.IpSecService; + +public class VcnTestUtils { + /** Mock system services by directly mocking the *Manager interface. */ + public static void setupSystemService( + Context mockContext, Object service, String name, Class<?> serviceClass) { + doReturn(name).when(mockContext).getSystemServiceName(serviceClass); + doReturn(service).when(mockContext).getSystemService(name); + } + + /** Mock IpSecService by mocking the underlying service binder. */ + public static IpSecManager setupIpSecManager(Context mockContext, IpSecService service) { + doReturn(Context.IPSEC_SERVICE).when(mockContext).getSystemServiceName(IpSecManager.class); + + final IpSecManager ipSecMgr = new IpSecManager(mockContext, service); + doReturn(ipSecMgr).when(mockContext).getSystemService(Context.IPSEC_SERVICE); + + // Return to ensure this doesn't get reaped. + return ipSecMgr; + } +} |