Merge "Verify VPN can handle network loss"

This commit is contained in:
Yan Yan
2022-06-21 17:36:22 +00:00
committed by Gerrit Code Review

View File

@@ -41,6 +41,7 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeTrue;
@@ -177,6 +178,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -279,6 +281,8 @@ public class VpnTest {
@Mock private ConnectivityManager mConnectivityManager; @Mock private ConnectivityManager mConnectivityManager;
@Mock private IpSecService mIpSecService; @Mock private IpSecService mIpSecService;
@Mock private VpnProfileStore mVpnProfileStore; @Mock private VpnProfileStore mVpnProfileStore;
@Mock private ScheduledThreadPoolExecutor mExecutor;
@Mock private ScheduledFuture mScheduledFuture;
@Mock DeviceIdleInternal mDeviceIdleInternal; @Mock DeviceIdleInternal mDeviceIdleInternal;
private final VpnProfile mVpnProfile; private final VpnProfile mVpnProfile;
@@ -342,7 +346,9 @@ public class VpnTest {
// PERMISSION_DENIED. // PERMISSION_DENIED.
doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(any()); doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(any());
// Set up mIkev2SessionCreator and mExecutor
resetIkev2SessionCreator(mIkeSessionWrapper); resetIkev2SessionCreator(mIkeSessionWrapper);
resetExecutor(mScheduledFuture);
} }
private void resetIkev2SessionCreator(Vpn.IkeSessionWrapper ikeSession) { private void resetIkev2SessionCreator(Vpn.IkeSessionWrapper ikeSession) {
@@ -351,6 +357,18 @@ public class VpnTest {
.thenReturn(ikeSession); .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 @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN); doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
@@ -1372,10 +1390,6 @@ public class VpnTest {
final ArgumentCaptor<IkeSessionCallback> captor = final ArgumentCaptor<IkeSessionCallback> captor =
ArgumentCaptor.forClass(IkeSessionCallback.class); 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); final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
.thenReturn(mVpnProfile.encode()); .thenReturn(mVpnProfile.encode());
@@ -1400,25 +1414,38 @@ public class VpnTest {
verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)) verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
.unregisterNetworkCallback(eq(cb)); .unregisterNetworkCallback(eq(cb));
} else if (errorType == VpnManager.ERROR_CLASS_RECOVERABLE) { } 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; int retryIndex = 0;
verify(mIkev2SessionCreator, final IkeSessionCallback ikeCb2 = verifyRetryAndGetNewIkeCb(retryIndex++);
timeout(((TestDeps) vpn.mDeps).getNextRetryDelaySeconds(retryIndex++) * 1000
+ TEST_TIMEOUT_MS))
.createIkeSession(any(), any(), any(), any(), captor.capture(), any());
// Capture a new IkeSessionCallback to get the latest token.
reset(mIkev2SessionCreator);
final IkeSessionCallback ikeCb2 = captor.getValue();
ikeCb2.onClosedWithException(exception); ikeCb2.onClosedWithException(exception);
verify(mIkev2SessionCreator, verifyRetryAndGetNewIkeCb(retryIndex++);
timeout(((TestDeps) vpn.mDeps).getNextRetryDelaySeconds(retryIndex++) * 1000
+ TEST_TIMEOUT_MS))
.createIkeSession(any(), any(), any(), any(), captor.capture(), any());
reset(mIkev2SessionCreator);
} }
} }
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 @Test
public void testStartPlatformVpnAuthenticationFailed() throws Exception { public void testStartPlatformVpnAuthenticationFailed() throws Exception {
final IkeProtocolException exception = mock(IkeProtocolException.class); final IkeProtocolException exception = mock(IkeProtocolException.class);
@@ -1685,9 +1712,13 @@ public class VpnTest {
final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */)); createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
// Mock network switch // Mock network loss and verify a cleanup task is scheduled
vpnSnapShot.nwCb.onLost(TEST_NETWORK); 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); vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
verify(mScheduledFuture).cancel(anyBoolean());
// Verify MOBIKE is triggered // Verify MOBIKE is triggered
verify(mIkeSessionWrapper).setNetwork(TEST_NETWORK_2); verify(mIkeSessionWrapper).setNetwork(TEST_NETWORK_2);
@@ -1755,7 +1786,55 @@ public class VpnTest {
vpnSnapShot.vpn.mVpnRunner.exitVpnRunner(); 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 @Test
public void testStartRacoonNumericAddress() throws Exception { public void testStartRacoonNumericAddress() throws Exception {
@@ -1981,16 +2060,7 @@ public class VpnTest {
@Override @Override
public ScheduledThreadPoolExecutor newScheduledThreadPoolExecutor() { public ScheduledThreadPoolExecutor newScheduledThreadPoolExecutor() {
final ScheduledThreadPoolExecutor mockExecutor = return mExecutor;
mock(ScheduledThreadPoolExecutor.class);
doAnswer(
(invocation) -> {
((Runnable) invocation.getArgument(0)).run();
return null;
})
.when(mockExecutor)
.execute(any());
return mockExecutor;
} }
} }