diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index e84df162e1..5e276fac04 100644 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -1477,7 +1477,7 @@ public class ConnectivityServiceTest { !mMockNetworkAgent.isBypassableVpn(), mVpnType)); updateState(NetworkInfo.DetailedState.CONNECTED, "registerAgent"); mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); - mNetworkAgent = mMockNetworkAgent.getNetworkAgent(); + mNetworkAgent = new Vpn.VpnNetworkAgentWrapper(mMockNetworkAgent.getNetworkAgent()); } private void registerAgent(Set uids) throws Exception { diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java index cdfa1904de..615e434809 100644 --- a/tests/unit/java/com/android/server/connectivity/VpnTest.java +++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java @@ -27,11 +27,14 @@ import static android.content.pm.UserInfo.FLAG_RESTRICTED; import static android.net.ConnectivityManager.NetworkCallback; import static android.net.INetd.IF_STATE_DOWN; import static android.net.INetd.IF_STATE_UP; +import static android.net.RouteInfo.RTN_UNREACHABLE; import static android.net.VpnManager.TYPE_VPN_PLATFORM; +import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE; import static android.os.Build.VERSION_CODES.S_V2; import static android.os.UserHandle.PER_USER_RANGE; import static com.android.modules.utils.build.SdkLevel.isAtLeastT; +import static com.android.server.connectivity.Vpn.VpnNetworkAgentWrapper; import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import static com.android.testutils.MiscAsserts.assertThrows; @@ -59,6 +62,7 @@ import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -83,7 +87,9 @@ import android.net.Ikev2VpnProfile; import android.net.InetAddresses; import android.net.InterfaceConfigurationParcel; import android.net.IpPrefix; +import android.net.IpSecConfig; import android.net.IpSecManager; +import android.net.IpSecTransform; import android.net.IpSecTunnelInterfaceResponse; import android.net.LinkAddress; import android.net.LinkProperties; @@ -100,7 +106,12 @@ import android.net.VpnManager; import android.net.VpnProfileState; import android.net.VpnService; import android.net.VpnTransportInfo; +import android.net.ipsec.ike.ChildSessionCallback; +import android.net.ipsec.ike.ChildSessionConfiguration; import android.net.ipsec.ike.IkeSessionCallback; +import android.net.ipsec.ike.IkeSessionConfiguration; +import android.net.ipsec.ike.IkeSessionConnectionInfo; +import android.net.ipsec.ike.IkeTrafficSelector; import android.net.ipsec.ike.exceptions.IkeException; import android.net.ipsec.ike.exceptions.IkeNetworkLostException; import android.net.ipsec.ike.exceptions.IkeNonProtocolException; @@ -121,6 +132,7 @@ import android.provider.Settings; import android.security.Credentials; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Pair; import android.util.Range; import androidx.test.filters.SmallTest; @@ -155,6 +167,7 @@ import java.io.FileDescriptor; import java.io.FileWriter; import java.io.IOException; import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; @@ -165,6 +178,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; @@ -198,11 +212,37 @@ public class VpnTest { static final Network EGRESS_NETWORK = new Network(101); static final String EGRESS_IFACE = "wlan0"; static final String TEST_VPN_PKG = "com.testvpn.vpn"; + private static final String TEST_VPN_CLIENT = "2.4.6.8"; private static final String TEST_VPN_SERVER = "1.2.3.4"; private static final String TEST_VPN_IDENTITY = "identity"; private static final byte[] TEST_VPN_PSK = "psk".getBytes(); + private static final int IP4_PREFIX_LEN = 32; + private static final int MIN_PORT = 0; + private static final int MAX_PORT = 65535; + + private static final InetAddress TEST_VPN_CLIENT_IP = + InetAddresses.parseNumericAddress(TEST_VPN_CLIENT); + private static final InetAddress TEST_VPN_SERVER_IP = + InetAddresses.parseNumericAddress(TEST_VPN_SERVER); + private static final InetAddress TEST_VPN_CLIENT_IP_2 = + InetAddresses.parseNumericAddress("192.0.2.200"); + private static final InetAddress TEST_VPN_SERVER_IP_2 = + InetAddresses.parseNumericAddress("192.0.2.201"); + private static final InetAddress TEST_VPN_INTERNAL_IP = + InetAddresses.parseNumericAddress("198.51.100.10"); + private static final InetAddress TEST_VPN_INTERNAL_DNS = + InetAddresses.parseNumericAddress("8.8.8.8"); + + private static final IkeTrafficSelector IN_TS = + new IkeTrafficSelector(MIN_PORT, MAX_PORT, TEST_VPN_INTERNAL_IP, TEST_VPN_INTERNAL_IP); + private static final IkeTrafficSelector OUT_TS = + new IkeTrafficSelector(MIN_PORT, MAX_PORT, + InetAddresses.parseNumericAddress("0.0.0.0"), + InetAddresses.parseNumericAddress("255.255.255.255")); + private static final Network TEST_NETWORK = new Network(Integer.MAX_VALUE); + private static final Network TEST_NETWORK_2 = new Network(Integer.MAX_VALUE - 1); private static final String TEST_IFACE_NAME = "TEST_IFACE"; private static final int TEST_TUNNEL_RESOURCE_ID = 0x2345; private static final long TEST_TIMEOUT_MS = 500L; @@ -234,7 +274,9 @@ public class VpnTest { @Mock private AppOpsManager mAppOps; @Mock private NotificationManager mNotificationManager; @Mock private Vpn.SystemServices mSystemServices; + @Mock private Vpn.IkeSessionWrapper mIkeSessionWrapper; @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator; + @Mock private Vpn.VpnNetworkAgentWrapper mVpnNetworkAgentWrapper; @Mock private ConnectivityManager mConnectivityManager; @Mock private IpSecService mIpSecService; @Mock private VpnProfileStore mVpnProfileStore; @@ -243,6 +285,8 @@ public class VpnTest { private IpSecManager mIpSecManager; + private TestDeps mTestDeps; + public VpnTest() throws Exception { // Build an actual VPN profile that is capable of being converted to and from an // Ikev2VpnProfile @@ -257,6 +301,7 @@ public class VpnTest { MockitoAnnotations.initMocks(this); mIpSecManager = new IpSecManager(mContext, mIpSecService); + mTestDeps = spy(new TestDeps()); when(mContext.getPackageManager()).thenReturn(mPackageManager); setMockedPackages(mPackages); @@ -297,6 +342,14 @@ public class VpnTest { // itself, so set the default value of Context#checkCallingOrSelfPermission to // PERMISSION_DENIED. doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(any()); + + resetIkev2SessionCreator(mIkeSessionWrapper); + } + + private void resetIkev2SessionCreator(Vpn.IkeSessionWrapper ikeSession) { + reset(mIkev2SessionCreator); + when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any())) + .thenReturn(ikeSession); } @After @@ -808,10 +861,11 @@ public class VpnTest { vpn.startVpnProfile(TEST_VPN_PKG); verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); - vpn.mNetworkAgent = new NetworkAgent(mContext, Looper.getMainLooper(), TAG, - new NetworkCapabilities.Builder().build(), new LinkProperties(), 10 /* score */, - new NetworkAgentConfig.Builder().build(), - new NetworkProvider(mContext, Looper.getMainLooper(), TAG)) {}; + vpn.mNetworkAgent = new VpnNetworkAgentWrapper( + new NetworkAgent(mContext, Looper.getMainLooper(), TAG, + new NetworkCapabilities.Builder().build(), new LinkProperties(), + 10 /* score */, new NetworkAgentConfig.Builder().build(), + new NetworkProvider(mContext, Looper.getMainLooper(), TAG)) {}); return vpn; } @@ -1320,6 +1374,10 @@ public class VpnTest { final ArgumentCaptor captor = ArgumentCaptor.forClass(IkeSessionCallback.class); + // This test depends on a real ScheduledThreadPoolExecutor + doReturn(new ScheduledThreadPoolExecutor(1)).when(mTestDeps) + .getScheduledThreadPoolExecutor(); + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) .thenReturn(mVpnProfile.encode()); @@ -1491,13 +1549,216 @@ public class VpnTest { return vpn; } + private IkeSessionConnectionInfo createIkeConnectInfo() { + return new IkeSessionConnectionInfo(TEST_VPN_CLIENT_IP, TEST_VPN_SERVER_IP, TEST_NETWORK); + } + + private IkeSessionConnectionInfo createNewIkeConnectInfo() { + return new IkeSessionConnectionInfo( + TEST_VPN_CLIENT_IP_2, TEST_VPN_SERVER_IP_2, TEST_NETWORK_2); + } + + private IkeSessionConfiguration createIkeConfig( + IkeSessionConnectionInfo ikeConnectInfo, boolean isMobikeEnabled) { + final IkeSessionConfiguration.Builder builder = + new IkeSessionConfiguration.Builder(ikeConnectInfo); + + if (isMobikeEnabled) { + builder.addIkeExtension(EXTENSION_TYPE_MOBIKE); + } + + return builder.build(); + } + + private ChildSessionConfiguration createChildConfig() { + return new ChildSessionConfiguration.Builder(Arrays.asList(IN_TS), Arrays.asList(OUT_TS)) + .addInternalAddress(new LinkAddress(TEST_VPN_INTERNAL_IP, IP4_PREFIX_LEN)) + .addInternalDnsServer(TEST_VPN_INTERNAL_DNS) + .build(); + } + + private IpSecTransform createIpSecTransform() { + return new IpSecTransform(mContext, new IpSecConfig()); + } + + private void verifyApplyTunnelModeTransforms(int expectedTimes) throws Exception { + verify(mIpSecService, times(expectedTimes)).applyTunnelModeTransform( + eq(TEST_TUNNEL_RESOURCE_ID), eq(IpSecManager.DIRECTION_IN), + anyInt(), anyString()); + verify(mIpSecService, times(expectedTimes)).applyTunnelModeTransform( + eq(TEST_TUNNEL_RESOURCE_ID), eq(IpSecManager.DIRECTION_OUT), + anyInt(), anyString()); + } + + private Pair verifyCreateIkeAndCaptureCbs() + throws Exception { + final ArgumentCaptor ikeCbCaptor = + ArgumentCaptor.forClass(IkeSessionCallback.class); + final ArgumentCaptor childCbCaptor = + ArgumentCaptor.forClass(ChildSessionCallback.class); + + verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS)).createIkeSession( + any(), any(), any(), any(), ikeCbCaptor.capture(), childCbCaptor.capture()); + final IkeSessionCallback ikeCb = ikeCbCaptor.getValue(); + final ChildSessionCallback childCb = childCbCaptor.getValue(); + + return new Pair<>(ikeCbCaptor.getValue(), childCbCaptor.getValue()); + } + + private static class PlatformVpnSnapshot { + public final Vpn vpn; + public final NetworkCallback nwCb; + public final IkeSessionCallback ikeCb; + public final ChildSessionCallback childCb; + + PlatformVpnSnapshot(Vpn vpn, NetworkCallback nwCb, + IkeSessionCallback ikeCb, ChildSessionCallback childCb) { + this.vpn = vpn; + this.nwCb = nwCb; + this.ikeCb = ikeCb; + this.childCb = childCb; + } + } + + private PlatformVpnSnapshot verifySetupPlatformVpn(IkeSessionConfiguration ikeConfig) + throws Exception { + doReturn(mVpnNetworkAgentWrapper).when(mTestDeps) + .getVpnNetworkAgentWrapper( + any(), any(), anyString(), any(), any(), any(), any(), any()); + + final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); + when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) + .thenReturn(mVpnProfile.encode()); + + vpn.startVpnProfile(TEST_VPN_PKG); + final NetworkCallback nwCb = triggerOnAvailableAndGetCallback(); + + // Mock the setup procedure by firing callbacks + final Pair cbPair = + verifyCreateIkeAndCaptureCbs(); + final IkeSessionCallback ikeCb = cbPair.first; + final ChildSessionCallback childCb = cbPair.second; + + ikeCb.onOpened(ikeConfig); + childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_IN); + childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_OUT); + childCb.onOpened(createChildConfig()); + + // Verification VPN setup + verifyApplyTunnelModeTransforms(1); + + ArgumentCaptor lpCaptor = ArgumentCaptor.forClass(LinkProperties.class); + ArgumentCaptor ncCaptor = + ArgumentCaptor.forClass(NetworkCapabilities.class); + verify(mTestDeps).getVpnNetworkAgentWrapper( + any(), any(), anyString(), ncCaptor.capture(), lpCaptor.capture(), + any(), any(), any()); + + // Check LinkProperties + final LinkProperties lp = lpCaptor.getValue(); + final List expectedRoutes = Arrays.asList( + new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null /*gateway*/, + TEST_IFACE_NAME, RouteInfo.RTN_UNICAST), + new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null /*gateway*/, + TEST_IFACE_NAME, RTN_UNREACHABLE)); + assertEquals(expectedRoutes, lp.getRoutes()); + + // Check internal addresses + final List expectedAddresses = + Arrays.asList(new LinkAddress(TEST_VPN_INTERNAL_IP, IP4_PREFIX_LEN)); + assertEquals(expectedAddresses, lp.getLinkAddresses()); + + // Check internal DNS + assertEquals(Arrays.asList(TEST_VPN_INTERNAL_DNS), lp.getDnsServers()); + + // Check NetworkCapabilities + assertEquals(Arrays.asList(TEST_NETWORK), ncCaptor.getValue().getUnderlyingNetworks()); + + return new PlatformVpnSnapshot(vpn, nwCb, ikeCb, childCb); + } + @Test public void testStartPlatformVpn() throws Exception { - startLegacyVpn(createVpn(primaryUser.id), mVpnProfile); - // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in - // a subsequent patch. + final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( + createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */)); + vpnSnapShot.vpn.mVpnRunner.exitVpnRunner(); } + @Test + public void testStartPlatformVpnMobility_mobikeEnabled() throws Exception { + final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( + createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */)); + + // Mock network switch + vpnSnapShot.nwCb.onLost(TEST_NETWORK); + vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2); + + // Verify MOBIKE is triggered + verify(mIkeSessionWrapper).setNetwork(TEST_NETWORK_2); + + // Mock the MOBIKE procedure + vpnSnapShot.ikeCb.onIkeSessionConnectionInfoChanged(createNewIkeConnectInfo()); + vpnSnapShot.childCb.onIpSecTransformsMigrated( + createIpSecTransform(), createIpSecTransform()); + + verify(mIpSecService).setNetworkForTunnelInterface( + eq(TEST_TUNNEL_RESOURCE_ID), eq(TEST_NETWORK_2), anyString()); + + // Expect 2 times: one for initial setup and one for MOBIKE + verifyApplyTunnelModeTransforms(2); + + // Verify mNetworkCapabilities and mNetworkAgent are updated + assertEquals( + Collections.singletonList(TEST_NETWORK_2), + vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks()); + verify(mVpnNetworkAgentWrapper) + .setUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2)); + + vpnSnapShot.vpn.mVpnRunner.exitVpnRunner(); + } + + @Test + public void testStartPlatformVpnReestablishes_mobikeDisabled() throws Exception { + final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn( + createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */)); + + // Forget the first IKE creation + resetIkev2SessionCreator(mock(Vpn.IkeSessionWrapper.class)); + + // Mock network switch + vpnSnapShot.nwCb.onLost(TEST_NETWORK); + vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2); + + // Verify the old IKE Session is killed + verify(mIkeSessionWrapper).kill(); + + final Pair cbPair = + verifyCreateIkeAndCaptureCbs(); + final IkeSessionCallback ikeCbNew = cbPair.first; + final ChildSessionCallback childCbNew = cbPair.second; + + // Mock the IKE Session setup + ikeCbNew.onOpened(createIkeConfig(createNewIkeConnectInfo(), false /* isMobikeEnabled */)); + + childCbNew.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_IN); + childCbNew.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_OUT); + childCbNew.onOpened(createChildConfig()); + + // Expect 2 times since there have been two Session setups + verifyApplyTunnelModeTransforms(2); + + // Verify mNetworkCapabilities and mNetworkAgent are updated + assertEquals( + Collections.singletonList(TEST_NETWORK_2), + vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks()); + verify(mVpnNetworkAgentWrapper) + .setUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2)); + + vpnSnapShot.vpn.mVpnRunner.exitVpnRunner(); + } + + // TODO: Add a test for network loss without mobility + @Test public void testStartRacoonNumericAddress() throws Exception { startRacoon("1.2.3.4", "1.2.3.4"); @@ -1578,7 +1839,8 @@ public class VpnTest { } } - private final class TestDeps extends Vpn.Dependencies { + // Make it public and un-final so as to spy it + public class TestDeps extends Vpn.Dependencies { public final CompletableFuture racoonArgs = new CompletableFuture(); public final CompletableFuture mtpdArgs = new CompletableFuture(); public final File mStateFile; @@ -1713,10 +1975,25 @@ public class VpnTest { return mDeviceIdleInternal; } + @Override public long getNextRetryDelaySeconds(int retryCount) { // Simply return retryCount as the delay seconds for retrying. return retryCount; } + + @Override + public ScheduledThreadPoolExecutor getScheduledThreadPoolExecutor() { + final ScheduledThreadPoolExecutor mockExecutor = + mock(ScheduledThreadPoolExecutor.class); + doAnswer( + (invocation) -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mockExecutor) + .execute(any()); + return mockExecutor; + } } /** @@ -1728,7 +2005,7 @@ public class VpnTest { when(mContext.createContextAsUser(eq(UserHandle.of(userId)), anyInt())) .thenReturn(asUserContext); final TestLooper testLooper = new TestLooper(); - final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, new TestDeps(), mNetService, + final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, mTestDeps, mNetService, mNetd, userId, mVpnProfileStore, mSystemServices, mIkev2SessionCreator); verify(mConnectivityManager, times(1)).registerNetworkProvider(argThat( provider -> provider.getName().contains("VpnNetworkProvider")