From 37a1ba22028ad2b01224eb1ee04abd1835b874a0 Mon Sep 17 00:00:00 2001 From: Yan Yan Date: Wed, 8 Jun 2022 23:54:57 +0000 Subject: [PATCH] Verify VPN can handle network loss This commit also - verifies that during network switch with MOBIKE enabled, the cleanup task is scheduled upon network loss and cancelled when new network is available. - improves the retry tests to avoid waiting for actual timeout Bug: 192077544 Test: atest VpnTest (new tests) Change-Id: I5b47ba98116ac4523a36bc495e8788f29a9ecf20 --- .../android/server/connectivity/VpnTest.java | 128 ++++++++++++++---- 1 file changed, 99 insertions(+), 29 deletions(-) diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java index 5899fd08aa..e8702b9fd7 100644 --- a/tests/unit/java/com/android/server/connectivity/VpnTest.java +++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java @@ -41,6 +41,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; @@ -177,6 +178,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; @@ -279,6 +281,8 @@ public class VpnTest { @Mock private ConnectivityManager mConnectivityManager; @Mock private IpSecService mIpSecService; @Mock private VpnProfileStore mVpnProfileStore; + @Mock private ScheduledThreadPoolExecutor mExecutor; + @Mock private ScheduledFuture mScheduledFuture; @Mock DeviceIdleInternal mDeviceIdleInternal; private final VpnProfile mVpnProfile; @@ -342,7 +346,9 @@ public class VpnTest { // PERMISSION_DENIED. doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(any()); + // Set up mIkev2SessionCreator and mExecutor resetIkev2SessionCreator(mIkeSessionWrapper); + resetExecutor(mScheduledFuture); } private void resetIkev2SessionCreator(Vpn.IkeSessionWrapper ikeSession) { @@ -351,6 +357,18 @@ public class VpnTest { .thenReturn(ikeSession); } + private void resetExecutor(ScheduledFuture scheduledFuture) { + doAnswer( + (invocation) -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mExecutor) + .execute(any()); + when(mExecutor.schedule( + any(Runnable.class), anyLong(), any())).thenReturn(mScheduledFuture); + } + @After public void tearDown() throws Exception { doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN); @@ -1372,10 +1390,6 @@ public class VpnTest { final ArgumentCaptor captor = ArgumentCaptor.forClass(IkeSessionCallback.class); - // This test depends on a real ScheduledThreadPoolExecutor - doReturn(new ScheduledThreadPoolExecutor(1)).when(mTestDeps) - .newScheduledThreadPoolExecutor(); - final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) .thenReturn(mVpnProfile.encode()); @@ -1400,25 +1414,38 @@ public class VpnTest { verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)) .unregisterNetworkCallback(eq(cb)); } else if (errorType == VpnManager.ERROR_CLASS_RECOVERABLE) { - // To prevent spending much time to test the retry function, only retry 2 times here. int retryIndex = 0; - verify(mIkev2SessionCreator, - timeout(((TestDeps) vpn.mDeps).getNextRetryDelaySeconds(retryIndex++) * 1000 - + TEST_TIMEOUT_MS)) - .createIkeSession(any(), any(), any(), any(), captor.capture(), any()); + final IkeSessionCallback ikeCb2 = verifyRetryAndGetNewIkeCb(retryIndex++); - // Capture a new IkeSessionCallback to get the latest token. - reset(mIkev2SessionCreator); - final IkeSessionCallback ikeCb2 = captor.getValue(); ikeCb2.onClosedWithException(exception); - verify(mIkev2SessionCreator, - timeout(((TestDeps) vpn.mDeps).getNextRetryDelaySeconds(retryIndex++) * 1000 - + TEST_TIMEOUT_MS)) - .createIkeSession(any(), any(), any(), any(), captor.capture(), any()); - reset(mIkev2SessionCreator); + verifyRetryAndGetNewIkeCb(retryIndex++); } } + private IkeSessionCallback verifyRetryAndGetNewIkeCb(int retryIndex) { + final ArgumentCaptor runnableCaptor = + ArgumentCaptor.forClass(Runnable.class); + final ArgumentCaptor ikeCbCaptor = + ArgumentCaptor.forClass(IkeSessionCallback.class); + + // Verify retry is scheduled + final long expectedDelay = mTestDeps.getNextRetryDelaySeconds(retryIndex); + verify(mExecutor).schedule(runnableCaptor.capture(), eq(expectedDelay), any()); + + // Mock the event of firing the retry task + runnableCaptor.getValue().run(); + + verify(mIkev2SessionCreator) + .createIkeSession(any(), any(), any(), any(), ikeCbCaptor.capture(), any()); + + // Forget the mIkev2SessionCreator#createIkeSession call and mExecutor#schedule call + // for the next retry verification + resetIkev2SessionCreator(mIkeSessionWrapper); + resetExecutor(mScheduledFuture); + + return ikeCbCaptor.getValue(); + } + @Test public void testStartPlatformVpnAuthenticationFailed() throws Exception { final IkeProtocolException exception = mock(IkeProtocolException.class); @@ -1685,9 +1712,13 @@ public class VpnTest { final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */)); - // Mock network switch + // Mock network loss and verify a cleanup task is scheduled vpnSnapShot.nwCb.onLost(TEST_NETWORK); + verify(mExecutor).schedule(any(Runnable.class), anyLong(), any()); + + // Mock new network comes up and the cleanup task is cancelled vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2); + verify(mScheduledFuture).cancel(anyBoolean()); // Verify MOBIKE is triggered verify(mIkeSessionWrapper).setNetwork(TEST_NETWORK_2); @@ -1755,7 +1786,55 @@ public class VpnTest { vpnSnapShot.vpn.mVpnRunner.exitVpnRunner(); } - // TODO: Add a test for network loss without mobility + private void verifyHandlingNetworkLoss() throws Exception { + final ArgumentCaptor lpCaptor = + ArgumentCaptor.forClass(LinkProperties.class); + verify(mMockNetworkAgent).sendLinkProperties(lpCaptor.capture()); + final LinkProperties lp = lpCaptor.getValue(); + + assertNull(lp.getInterfaceName()); + final List expectedRoutes = Arrays.asList( + new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null /*gateway*/, + null /*iface*/, RTN_UNREACHABLE), + new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null /*gateway*/, + null /*iface*/, RTN_UNREACHABLE)); + assertEquals(expectedRoutes, lp.getRoutes()); + } + + @Test + public void testStartPlatformVpnHandlesNetworkLoss_mobikeEnabled() throws Exception { + final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( + createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */)); + + // Forget the #sendLinkProperties during first setup. + reset(mMockNetworkAgent); + + final ArgumentCaptor runnableCaptor = + ArgumentCaptor.forClass(Runnable.class); + + // Mock network loss + vpnSnapShot.nwCb.onLost(TEST_NETWORK); + + // Mock the grace period expires + verify(mExecutor).schedule(runnableCaptor.capture(), anyLong(), any()); + runnableCaptor.getValue().run(); + + verifyHandlingNetworkLoss(); + } + + @Test + public void testStartPlatformVpnHandlesNetworkLoss_mobikeDisabled() throws Exception { + final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( + createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */)); + + // Forget the #sendLinkProperties during first setup. + reset(mMockNetworkAgent); + + // Mock network loss + vpnSnapShot.nwCb.onLost(TEST_NETWORK); + + verifyHandlingNetworkLoss(); + } @Test public void testStartRacoonNumericAddress() throws Exception { @@ -1981,16 +2060,7 @@ public class VpnTest { @Override public ScheduledThreadPoolExecutor newScheduledThreadPoolExecutor() { - final ScheduledThreadPoolExecutor mockExecutor = - mock(ScheduledThreadPoolExecutor.class); - doAnswer( - (invocation) -> { - ((Runnable) invocation.getArgument(0)).run(); - return null; - }) - .when(mockExecutor) - .execute(any()); - return mockExecutor; + return mExecutor; } }