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
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 5899fd0..e8702b9 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.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.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 @@
@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 @@
// 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 @@
.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 @@
final ArgumentCaptor<IkeSessionCallback> 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 @@
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<Runnable> runnableCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+ final ArgumentCaptor<IkeSessionCallback> 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 @@
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 @@
vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
}
- // TODO: Add a test for network loss without mobility
+ private void verifyHandlingNetworkLoss() throws Exception {
+ final ArgumentCaptor<LinkProperties> lpCaptor =
+ ArgumentCaptor.forClass(LinkProperties.class);
+ verify(mMockNetworkAgent).sendLinkProperties(lpCaptor.capture());
+ final LinkProperties lp = lpCaptor.getValue();
+
+ assertNull(lp.getInterfaceName());
+ final List<RouteInfo> 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<Runnable> 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 @@
@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;
}
}