blob: caef54a7a6d7216d76df7da701de66dc36538f8c [file] [log] [blame]
/*
* Copyright (C) 2016 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.connectivity;
import static android.Manifest.permission.BIND_VPN_SERVICE;
import static android.Manifest.permission.CONTROL_VPN;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
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.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import static com.android.testutils.MiscAsserts.assertThrows;
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;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.after;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
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;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.INetd;
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;
import android.net.LocalSocket;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo.DetailedState;
import android.net.RouteInfo;
import android.net.UidRangeParcel;
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;
import android.net.ipsec.ike.exceptions.IkeProtocolException;
import android.net.ipsec.ike.exceptions.IkeTimeoutException;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.INetworkManagementService;
import android.os.ParcelFileDescriptor;
import android.os.PowerWhitelistManager;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.test.TestLooper;
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;
import com.android.internal.R;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import com.android.internal.util.HexDump;
import com.android.modules.utils.build.SdkLevel;
import com.android.server.DeviceIdleInternal;
import com.android.server.IpSecService;
import com.android.server.VpnTestBase;
import com.android.server.vcn.util.PersistableBundleUtils;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.AdditionalAnswers;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.BufferedWriter;
import java.io.File;
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;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
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;
/**
* Tests for {@link Vpn}.
*
* Build, install and run with:
* runtest frameworks-net -c com.android.server.connectivity.VpnTest
*/
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@IgnoreUpTo(S_V2)
public class VpnTest extends VpnTestBase {
private static final String TAG = "VpnTest";
@Rule
public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
static final Network EGRESS_NETWORK = new Network(101);
static final String EGRESS_IFACE = "wlan0";
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;
private static final String PRIMARY_USER_APP_EXCLUDE_KEY =
"VPNAPPEXCLUDED_27_com.testvpn.vpn";
static final String PKGS_BYTES = getPackageByteString(List.of(PKGS));
private static final Range<Integer> PRIMARY_USER_RANGE = uidRangeForUser(PRIMARY_USER.id);
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext;
@Mock private UserManager mUserManager;
@Mock private PackageManager mPackageManager;
@Mock private INetworkManagementService mNetService;
@Mock private INetd mNetd;
@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 mMockNetworkAgent;
@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;
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
final Ikev2VpnProfile.Builder builder =
new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY);
builder.setAuthPsk(TEST_VPN_PSK);
mVpnProfile = builder.build().toVpnProfile();
}
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mIpSecManager = new IpSecManager(mContext, mIpSecService);
mTestDeps = spy(new TestDeps());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
setMockedPackages(sPackages);
when(mContext.getPackageName()).thenReturn(TEST_VPN_PKG);
when(mContext.getOpPackageName()).thenReturn(TEST_VPN_PKG);
mockService(UserManager.class, Context.USER_SERVICE, mUserManager);
mockService(AppOpsManager.class, Context.APP_OPS_SERVICE, mAppOps);
mockService(NotificationManager.class, Context.NOTIFICATION_SERVICE, mNotificationManager);
mockService(ConnectivityManager.class, Context.CONNECTIVITY_SERVICE, mConnectivityManager);
mockService(IpSecManager.class, Context.IPSEC_SERVICE, mIpSecManager);
when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent))
.thenReturn(Resources.getSystem().getString(
R.string.config_customVpnAlwaysOnDisconnectedDialogComponent));
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS))
.thenReturn(true);
// Used by {@link Notification.Builder}
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
when(mContext.getApplicationInfo()).thenReturn(applicationInfo);
when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
.thenReturn(applicationInfo);
doNothing().when(mNetService).registerObserver(any());
// Deny all appops by default.
when(mAppOps.noteOpNoThrow(anyString(), anyInt(), anyString(), any(), any()))
.thenReturn(AppOpsManager.MODE_IGNORED);
// Setup IpSecService
final IpSecTunnelInterfaceResponse tunnelResp =
new IpSecTunnelInterfaceResponse(
IpSecManager.Status.OK, TEST_TUNNEL_RESOURCE_ID, TEST_IFACE_NAME);
when(mIpSecService.createTunnelInterface(any(), any(), any(), any(), any()))
.thenReturn(tunnelResp);
// The unit test should know what kind of permission it needs and set the permission by
// itself, so set the default value of Context#checkCallingOrSelfPermission to
// PERMISSION_DENIED.
doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(any());
// Set up mIkev2SessionCreator and mExecutor
resetIkev2SessionCreator(mIkeSessionWrapper);
resetExecutor(mScheduledFuture);
}
private void resetIkev2SessionCreator(Vpn.IkeSessionWrapper ikeSession) {
reset(mIkev2SessionCreator);
when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any()))
.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);
}
private <T> void mockService(Class<T> clazz, String name, T service) {
doReturn(service).when(mContext).getSystemService(name);
doReturn(name).when(mContext).getSystemServiceName(clazz);
if (mContext.getSystemService(clazz).getClass().equals(Object.class)) {
// Test is using mockito-extended (mContext uses Answers.RETURNS_DEEP_STUBS and returned
// a mock object on a final method)
doCallRealMethod().when(mContext).getSystemService(clazz);
}
}
private Set<Range<Integer>> rangeSet(Range<Integer> ... ranges) {
final Set<Range<Integer>> range = new ArraySet<>();
for (Range<Integer> r : ranges) range.add(r);
return range;
}
private static Range<Integer> uidRangeForUser(int userId) {
return new Range<Integer>(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1);
}
private Range<Integer> uidRange(int start, int stop) {
return new Range<Integer>(start, stop);
}
private static String getPackageByteString(List<String> packages) {
try {
return HexDump.toHexString(
PersistableBundleUtils.toDiskStableBytes(PersistableBundleUtils.fromList(
packages, PersistableBundleUtils.STRING_SERIALIZER)),
true /* upperCase */);
} catch (IOException e) {
return null;
}
}
@Test
public void testRestrictedProfilesAreAddedToVpn() {
setMockedUsers(PRIMARY_USER, SECONDARY_USER, RESTRICTED_PROFILE_A, RESTRICTED_PROFILE_B);
final Vpn vpn = createVpn(PRIMARY_USER.id);
// Assume the user can have restricted profiles.
doReturn(true).when(mUserManager).canHaveRestrictedProfile();
final Set<Range<Integer>> ranges =
vpn.createUserAndRestrictedProfilesRanges(PRIMARY_USER.id, null, null);
assertEquals(rangeSet(PRIMARY_USER_RANGE, uidRangeForUser(RESTRICTED_PROFILE_A.id)),
ranges);
}
@Test
public void testManagedProfilesAreNotAddedToVpn() {
setMockedUsers(PRIMARY_USER, MANAGED_PROFILE_A);
final Vpn vpn = createVpn(PRIMARY_USER.id);
final Set<Range<Integer>> ranges = vpn.createUserAndRestrictedProfilesRanges(
PRIMARY_USER.id, null, null);
assertEquals(rangeSet(PRIMARY_USER_RANGE), ranges);
}
@Test
public void testAddUserToVpnOnlyAddsOneUser() {
setMockedUsers(PRIMARY_USER, RESTRICTED_PROFILE_A, MANAGED_PROFILE_A);
final Vpn vpn = createVpn(PRIMARY_USER.id);
final Set<Range<Integer>> ranges = new ArraySet<>();
vpn.addUserToRanges(ranges, PRIMARY_USER.id, null, null);
assertEquals(rangeSet(PRIMARY_USER_RANGE), ranges);
}
@Test
public void testUidAllowAndDenylist() throws Exception {
final Vpn vpn = createVpn(PRIMARY_USER.id);
final Range<Integer> user = PRIMARY_USER_RANGE;
final int userStart = user.getLower();
final int userStop = user.getUpper();
final String[] packages = {PKGS[0], PKGS[1], PKGS[2]};
// Allowed list
final Set<Range<Integer>> allow = vpn.createUserAndRestrictedProfilesRanges(PRIMARY_USER.id,
Arrays.asList(packages), null /* disallowedApplications */);
assertEquals(rangeSet(
uidRange(userStart + PKG_UIDS[0], userStart + PKG_UIDS[0]),
uidRange(userStart + PKG_UIDS[1], userStart + PKG_UIDS[2]),
uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0]),
Process.toSdkSandboxUid(userStart + PKG_UIDS[0])),
uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[1]),
Process.toSdkSandboxUid(userStart + PKG_UIDS[2]))),
allow);
// Denied list
final Set<Range<Integer>> disallow =
vpn.createUserAndRestrictedProfilesRanges(PRIMARY_USER.id,
null /* allowedApplications */, Arrays.asList(packages));
assertEquals(rangeSet(
uidRange(userStart, userStart + PKG_UIDS[0] - 1),
uidRange(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
/* Empty range between UIDS[1] and UIDS[2], should be excluded, */
uidRange(userStart + PKG_UIDS[2] + 1,
Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)),
disallow);
}
private void verifyPowerSaveTempWhitelistApp(String packageName) {
verify(mDeviceIdleInternal).addPowerSaveTempWhitelistApp(anyInt(), eq(packageName),
anyLong(), anyInt(), eq(false), eq(PowerWhitelistManager.REASON_VPN),
eq("VpnManager event"));
}
@Test
public void testGetAlwaysAndOnGetLockDown() throws Exception {
final Vpn vpn = createVpn(PRIMARY_USER.id);
// Default state.
assertFalse(vpn.getAlwaysOn());
assertFalse(vpn.getLockdown());
// Set always-on without lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList()));
assertTrue(vpn.getAlwaysOn());
assertFalse(vpn.getLockdown());
// Set always-on with lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList()));
assertTrue(vpn.getAlwaysOn());
assertTrue(vpn.getLockdown());
// Remove always-on configuration.
assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
assertFalse(vpn.getAlwaysOn());
assertFalse(vpn.getLockdown());
}
@Test
public void testLockdownChangingPackage() throws Exception {
final Vpn vpn = createVpn(PRIMARY_USER.id);
final Range<Integer> user = PRIMARY_USER_RANGE;
final int userStart = user.getLower();
final int userStop = user.getUpper();
// Set always-on without lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null));
// Set always-on with lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
}));
// Switch to another app.
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[3] - 1),
new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
}));
}
@Test
public void testLockdownAllowlist() throws Exception {
final Vpn vpn = createVpn(PRIMARY_USER.id);
final Range<Integer> user = PRIMARY_USER_RANGE;
final int userStart = user.getLower();
final int userStop = user.getUpper();
// Set always-on with lockdown and allow app PKGS[2] from lockdown.
assertTrue(vpn.setAlwaysOnPackage(
PKGS[1], true, Collections.singletonList(PKGS[2])));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
Process.toSdkSandboxUid(userStart + PKG_UIDS[1]) - 1),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
}));
// Change allowed app list to PKGS[3].
assertTrue(vpn.setAlwaysOnPackage(
PKGS[1], true, Collections.singletonList(PKGS[3])));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1),
new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1),
Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
}));
// Change the VPN app.
assertTrue(vpn.setAlwaysOnPackage(
PKGS[0], true, Collections.singletonList(PKGS[3])));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1),
new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1),
Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1))
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart, userStart + PKG_UIDS[0] - 1),
new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1),
new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1))
}));
// Remove the list of allowed packages.
assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1),
new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[0] + 1,
Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop),
}));
// Add the list of allowed packages.
assertTrue(vpn.setAlwaysOnPackage(
PKGS[0], true, Collections.singletonList(PKGS[1])));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[0] + 1,
Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop),
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
}));
// Try allowing a package with a comma, should be rejected.
assertFalse(vpn.setAlwaysOnPackage(
PKGS[0], true, Collections.singletonList("a.b,c.d")));
// Pass a non-existent packages in the allowlist, they (and only they) should be ignored.
// allowed package should change from PGKS[1] to PKGS[2].
assertTrue(vpn.setAlwaysOnPackage(
PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
}));
verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[2] - 1),
new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
Process.toSdkSandboxUid(userStart + PKG_UIDS[2] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
}));
}
@Test
public void testLockdownRuleRepeatability() throws Exception {
final Vpn vpn = createVpn(PRIMARY_USER.id);
final UidRangeParcel[] primaryUserRangeParcel = new UidRangeParcel[] {
new UidRangeParcel(PRIMARY_USER_RANGE.getLower(), PRIMARY_USER_RANGE.getUpper())};
// Given legacy lockdown is already enabled,
vpn.setLockdown(true);
verify(mConnectivityManager, times(1)).setRequireVpnForUids(true,
toRanges(primaryUserRangeParcel));
// Enabling legacy lockdown twice should do nothing.
vpn.setLockdown(true);
verify(mConnectivityManager, times(1)).setRequireVpnForUids(anyBoolean(), any());
// And disabling should remove the rules exactly once.
vpn.setLockdown(false);
verify(mConnectivityManager, times(1)).setRequireVpnForUids(false,
toRanges(primaryUserRangeParcel));
// Removing the lockdown again should have no effect.
vpn.setLockdown(false);
verify(mConnectivityManager, times(2)).setRequireVpnForUids(anyBoolean(), any());
}
private ArrayList<Range<Integer>> toRanges(UidRangeParcel[] ranges) {
ArrayList<Range<Integer>> rangesArray = new ArrayList<>(ranges.length);
for (int i = 0; i < ranges.length; i++) {
rangesArray.add(new Range<>(ranges[i].start, ranges[i].stop));
}
return rangesArray;
}
@Test
public void testLockdownRuleReversibility() throws Exception {
doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
final Vpn vpn = createVpn(PRIMARY_USER.id);
final UidRangeParcel[] entireUser = {
new UidRangeParcel(PRIMARY_USER_RANGE.getLower(), PRIMARY_USER_RANGE.getUpper())
};
final UidRangeParcel[] exceptPkg0 = {
new UidRangeParcel(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1),
new UidRangeParcel(entireUser[0].start + PKG_UIDS[0] + 1,
Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] - 1)),
new UidRangeParcel(Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] + 1),
entireUser[0].stop),
};
final InOrder order = inOrder(mConnectivityManager);
// Given lockdown is enabled with no package (legacy VPN),
vpn.setLockdown(true);
order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(entireUser));
// When a new VPN package is set the rules should change to cover that package.
vpn.prepare(null, PKGS[0], VpnManager.TYPE_VPN_SERVICE);
order.verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(entireUser));
order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(exceptPkg0));
// When that VPN package is unset, everything should be undone again in reverse.
vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE);
order.verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(exceptPkg0));
order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(entireUser));
}
@Test
public void testPrepare_throwSecurityExceptionWhenGivenPackageDoesNotBelongToTheCaller()
throws Exception {
assumeTrue(isAtLeastT());
final Vpn vpn = createVpnAndSetupUidChecks();
assertThrows(SecurityException.class,
() -> vpn.prepare("com.not.vpn.owner", null, VpnManager.TYPE_VPN_SERVICE));
assertThrows(SecurityException.class,
() -> vpn.prepare(null, "com.not.vpn.owner", VpnManager.TYPE_VPN_SERVICE));
assertThrows(SecurityException.class,
() -> vpn.prepare("com.not.vpn.owner1", "com.not.vpn.owner2",
VpnManager.TYPE_VPN_SERVICE));
}
@Test
public void testPrepare_bothOldPackageAndNewPackageAreNull() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
assertTrue(vpn.prepare(null, null, VpnManager.TYPE_VPN_SERVICE));
}
@Test
public void testIsAlwaysOnPackageSupported() throws Exception {
final Vpn vpn = createVpn(PRIMARY_USER.id);
ApplicationInfo appInfo = new ApplicationInfo();
when(mPackageManager.getApplicationInfoAsUser(eq(PKGS[0]), anyInt(), eq(PRIMARY_USER.id)))
.thenReturn(appInfo);
ServiceInfo svcInfo = new ServiceInfo();
ResolveInfo resInfo = new ResolveInfo();
resInfo.serviceInfo = svcInfo;
when(mPackageManager.queryIntentServicesAsUser(any(), eq(PackageManager.GET_META_DATA),
eq(PRIMARY_USER.id)))
.thenReturn(Collections.singletonList(resInfo));
// null package name should return false
assertFalse(vpn.isAlwaysOnPackageSupported(null));
// Pre-N apps are not supported
appInfo.targetSdkVersion = VERSION_CODES.M;
assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
// N+ apps are supported by default
appInfo.targetSdkVersion = VERSION_CODES.N;
assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0]));
// Apps that opt out explicitly are not supported
appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
Bundle metaData = new Bundle();
metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false);
svcInfo.metaData = metaData;
assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
}
@Test
public void testNotificationShownForAlwaysOnApp() throws Exception {
final UserHandle userHandle = UserHandle.of(PRIMARY_USER.id);
final Vpn vpn = createVpn(PRIMARY_USER.id);
setMockedUsers(PRIMARY_USER);
final InOrder order = inOrder(mNotificationManager);
// Don't show a notification for regular disconnected states.
vpn.updateState(DetailedState.DISCONNECTED, TAG);
order.verify(mNotificationManager, atLeastOnce()).cancel(anyString(), anyInt());
// Start showing a notification for disconnected once always-on.
vpn.setAlwaysOnPackage(PKGS[0], false, null);
order.verify(mNotificationManager).notify(anyString(), anyInt(), any());
// Stop showing the notification once connected.
vpn.updateState(DetailedState.CONNECTED, TAG);
order.verify(mNotificationManager).cancel(anyString(), anyInt());
// Show the notification if we disconnect again.
vpn.updateState(DetailedState.DISCONNECTED, TAG);
order.verify(mNotificationManager).notify(anyString(), anyInt(), any());
// Notification should be cleared after unsetting always-on package.
vpn.setAlwaysOnPackage(null, false, null);
order.verify(mNotificationManager).cancel(anyString(), anyInt());
}
/**
* The profile name should NOT change between releases for backwards compatibility
*
* <p>If this is changed between releases, the {@link Vpn#getVpnProfilePrivileged()} method MUST
* be updated to ensure backward compatibility.
*/
@Test
public void testGetProfileNameForPackage() throws Exception {
final Vpn vpn = createVpn(PRIMARY_USER.id);
setMockedUsers(PRIMARY_USER);
final String expected = Credentials.PLATFORM_VPN + PRIMARY_USER.id + "_" + TEST_VPN_PKG;
assertEquals(expected, vpn.getProfileNameForPackage(TEST_VPN_PKG));
}
private Vpn createVpnAndSetupUidChecks(String... grantedOps) throws Exception {
return createVpnAndSetupUidChecks(PRIMARY_USER, grantedOps);
}
private Vpn createVpnAndSetupUidChecks(UserInfo user, String... grantedOps) throws Exception {
final Vpn vpn = createVpn(user.id);
setMockedUsers(user);
when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
.thenReturn(Process.myUid());
for (final String opStr : grantedOps) {
when(mAppOps.noteOpNoThrow(opStr, Process.myUid(), TEST_VPN_PKG,
null /* attributionTag */, null /* message */))
.thenReturn(AppOpsManager.MODE_ALLOWED);
}
return vpn;
}
private void checkProvisionVpnProfile(Vpn vpn, boolean expectedResult, String... checkedOps) {
assertEquals(expectedResult, vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile));
// The profile should always be stored, whether or not consent has been previously granted.
verify(mVpnProfileStore)
.put(
eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)),
eq(mVpnProfile.encode()));
for (final String checkedOpStr : checkedOps) {
verify(mAppOps).noteOpNoThrow(checkedOpStr, Process.myUid(), TEST_VPN_PKG,
null /* attributionTag */, null /* message */);
}
}
@Test
public void testProvisionVpnProfileNoIpsecTunnels() throws Exception {
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS))
.thenReturn(false);
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
try {
checkProvisionVpnProfile(
vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
fail("Expected exception due to missing feature");
} catch (UnsupportedOperationException expected) {
}
}
private Vpn prepareVpnForVerifyAppExclusionList() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
.thenReturn(mVpnProfile.encode());
when(mVpnProfileStore.get(PRIMARY_USER_APP_EXCLUDE_KEY))
.thenReturn(HexDump.hexStringToByteArray(PKGS_BYTES));
vpn.startVpnProfile(TEST_VPN_PKG);
verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
vpn.mNetworkAgent = mMockNetworkAgent;
return vpn;
}
@Test
public void testSetAndGetAppExclusionList() throws Exception {
final Vpn vpn = prepareVpnForVerifyAppExclusionList();
verify(mVpnProfileStore, never()).put(eq(PRIMARY_USER_APP_EXCLUDE_KEY), any());
vpn.setAppExclusionList(TEST_VPN_PKG, Arrays.asList(PKGS));
verify(mVpnProfileStore)
.put(eq(PRIMARY_USER_APP_EXCLUDE_KEY),
eq(HexDump.hexStringToByteArray(PKGS_BYTES)));
assertEquals(vpn.createUserAndRestrictedProfilesRanges(
PRIMARY_USER.id, null, Arrays.asList(PKGS)),
vpn.mNetworkCapabilities.getUids());
assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
}
@Test
public void testRefreshPlatformVpnAppExclusionList_updatesExcludedUids() throws Exception {
final Vpn vpn = prepareVpnForVerifyAppExclusionList();
vpn.setAppExclusionList(TEST_VPN_PKG, Arrays.asList(PKGS));
verify(mMockNetworkAgent).doSendNetworkCapabilities(any());
assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
reset(mMockNetworkAgent);
// Remove one of the package
List<Integer> newExcludedUids = toList(PKG_UIDS);
newExcludedUids.remove((Integer) PKG_UIDS[0]);
sPackages.remove(PKGS[0]);
vpn.refreshPlatformVpnAppExclusionList();
// List in keystore is not changed, but UID for the removed packages is no longer exempted.
assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
assertEquals(makeVpnUidRange(PRIMARY_USER.id, newExcludedUids),
vpn.mNetworkCapabilities.getUids());
ArgumentCaptor<NetworkCapabilities> ncCaptor =
ArgumentCaptor.forClass(NetworkCapabilities.class);
verify(mMockNetworkAgent).doSendNetworkCapabilities(ncCaptor.capture());
assertEquals(makeVpnUidRange(PRIMARY_USER.id, newExcludedUids),
ncCaptor.getValue().getUids());
reset(mMockNetworkAgent);
// Add the package back
newExcludedUids.add(PKG_UIDS[0]);
sPackages.put(PKGS[0], PKG_UIDS[0]);
vpn.refreshPlatformVpnAppExclusionList();
// List in keystore is not changed and the uid list should be updated in the net cap.
assertEquals(Arrays.asList(PKGS), vpn.getAppExclusionList(TEST_VPN_PKG));
assertEquals(makeVpnUidRange(PRIMARY_USER.id, newExcludedUids),
vpn.mNetworkCapabilities.getUids());
verify(mMockNetworkAgent).doSendNetworkCapabilities(ncCaptor.capture());
assertEquals(makeVpnUidRange(PRIMARY_USER.id, newExcludedUids),
ncCaptor.getValue().getUids());
}
private Set<Range<Integer>> makeVpnUidRange(int userId, List<Integer> excludedList) {
final SortedSet<Integer> list = new TreeSet<>();
final int userBase = userId * UserHandle.PER_USER_RANGE;
for (int uid : excludedList) {
final int applicationUid = UserHandle.getUid(userId, uid);
list.add(applicationUid);
list.add(Process.toSdkSandboxUid(applicationUid)); // Add Sdk Sandbox UID
}
final int minUid = userBase;
final int maxUid = userBase + UserHandle.PER_USER_RANGE - 1;
final Set<Range<Integer>> ranges = new ArraySet<>();
// Iterate the list to create the ranges between each uid.
int start = minUid;
for (int uid : list) {
if (uid == start) {
start++;
} else {
ranges.add(new Range<>(start, uid - 1));
start = uid + 1;
}
}
// Create the range between last uid and max uid.
if (start <= maxUid) {
ranges.add(new Range<>(start, maxUid));
}
return ranges;
}
@Test
public void testSetAndGetAppExclusionListRestrictedUser() throws Exception {
final Vpn vpn = prepareVpnForVerifyAppExclusionList();
// Mock it to restricted profile
when(mUserManager.getUserInfo(anyInt())).thenReturn(RESTRICTED_PROFILE_A);
// Restricted users cannot configure VPNs
assertThrows(SecurityException.class,
() -> vpn.setAppExclusionList(TEST_VPN_PKG, new ArrayList<>()));
assertThrows(SecurityException.class, () -> vpn.getAppExclusionList(TEST_VPN_PKG));
}
@Test
public void testProvisionVpnProfilePreconsented() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
checkProvisionVpnProfile(
vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
}
@Test
public void testProvisionVpnProfileNotPreconsented() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
// Expect that both the ACTIVATE_VPN and ACTIVATE_PLATFORM_VPN were tried, but the caller
// had neither.
checkProvisionVpnProfile(vpn, false /* expectedResult */,
AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN, AppOpsManager.OPSTR_ACTIVATE_VPN);
}
private void setAppOpsPermission() {
doAnswer(invocation -> {
when(mAppOps.noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN,
Process.myUid(), TEST_VPN_PKG,
null /* attributionTag */, null /* message */))
.thenReturn(AppOpsManager.MODE_ALLOWED);
return null;
}).when(mAppOps).setMode(
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
eq(Process.myUid()),
eq(TEST_VPN_PKG),
eq(AppOpsManager.MODE_ALLOWED));
}
@Test
public void testProvisionVpnProfileNotPreconsented_withControlVpnPermission() throws Exception {
setAppOpsPermission();
doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
final Vpn vpn = createVpnAndSetupUidChecks();
// ACTIVATE_PLATFORM_VPN will be granted if VPN app has CONTROL_VPN permission.
checkProvisionVpnProfile(vpn, true /* expectedResult */,
AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
}
@Test
public void testProvisionVpnProfileVpnServicePreconsented() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN);
checkProvisionVpnProfile(vpn, true /* expectedResult */, AppOpsManager.OPSTR_ACTIVATE_VPN);
}
@Test
public void testProvisionVpnProfileTooLarge() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
final VpnProfile bigProfile = new VpnProfile("");
bigProfile.name = new String(new byte[Vpn.MAX_VPN_PROFILE_SIZE_BYTES + 1]);
try {
vpn.provisionVpnProfile(TEST_VPN_PKG, bigProfile);
fail("Expected IAE due to profile size");
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testProvisionVpnProfileRestrictedUser() throws Exception {
final Vpn vpn =
createVpnAndSetupUidChecks(
RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
try {
vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile);
fail("Expected SecurityException due to restricted user");
} catch (SecurityException expected) {
}
}
@Test
public void testDeleteVpnProfile() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
vpn.deleteVpnProfile(TEST_VPN_PKG);
verify(mVpnProfileStore)
.remove(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
}
@Test
public void testDeleteVpnProfileRestrictedUser() throws Exception {
final Vpn vpn =
createVpnAndSetupUidChecks(
RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
try {
vpn.deleteVpnProfile(TEST_VPN_PKG);
fail("Expected SecurityException due to restricted user");
} catch (SecurityException expected) {
}
}
@Test
public void testGetVpnProfilePrivileged() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
.thenReturn(new VpnProfile("").encode());
vpn.getVpnProfilePrivileged(TEST_VPN_PKG);
verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
}
private void verifyPlatformVpnIsActivated(String packageName) {
verify(mAppOps).noteOpNoThrow(
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
eq(Process.myUid()),
eq(packageName),
eq(null) /* attributionTag */,
eq(null) /* message */);
verify(mAppOps).startOp(
eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
eq(Process.myUid()),
eq(packageName),
eq(null) /* attributionTag */,
eq(null) /* message */);
}
private void verifyPlatformVpnIsDeactivated(String packageName) {
// Add a small delay to double confirm that finishOp is only called once.
verify(mAppOps, after(100)).finishOp(
eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
eq(Process.myUid()),
eq(packageName),
eq(null) /* attributionTag */);
}
@Test
public void testStartVpnProfile() throws Exception {
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);
verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
verifyPlatformVpnIsActivated(TEST_VPN_PKG);
}
@Test
public void testStartVpnProfileVpnServicePreconsented() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN);
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
.thenReturn(mVpnProfile.encode());
vpn.startVpnProfile(TEST_VPN_PKG);
// Verify that the ACTIVATE_VPN appop was checked, but no error was thrown.
verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(),
TEST_VPN_PKG, null /* attributionTag */, null /* message */);
}
@Test
public void testStartVpnProfileNotConsented() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
try {
vpn.startVpnProfile(TEST_VPN_PKG);
fail("Expected failure due to no user consent");
} catch (SecurityException expected) {
}
// Verify both appops were checked.
verify(mAppOps)
.noteOpNoThrow(
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
eq(Process.myUid()),
eq(TEST_VPN_PKG),
eq(null) /* attributionTag */,
eq(null) /* message */);
verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(),
TEST_VPN_PKG, null /* attributionTag */, null /* message */);
// Keystore should never have been accessed.
verify(mVpnProfileStore, never()).get(any());
}
@Test
public void testStartVpnProfileMissingProfile() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))).thenReturn(null);
try {
vpn.startVpnProfile(TEST_VPN_PKG);
fail("Expected failure due to missing profile");
} catch (IllegalArgumentException expected) {
}
verify(mVpnProfileStore).get(vpn.getProfileNameForPackage(TEST_VPN_PKG));
verify(mAppOps)
.noteOpNoThrow(
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
eq(Process.myUid()),
eq(TEST_VPN_PKG),
eq(null) /* attributionTag */,
eq(null) /* message */);
}
@Test
public void testStartVpnProfileRestrictedUser() throws Exception {
final Vpn vpn =
createVpnAndSetupUidChecks(
RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
try {
vpn.startVpnProfile(TEST_VPN_PKG);
fail("Expected SecurityException due to restricted user");
} catch (SecurityException expected) {
}
}
@Test
public void testStopVpnProfileRestrictedUser() throws Exception {
final Vpn vpn =
createVpnAndSetupUidChecks(
RESTRICTED_PROFILE_A, AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
try {
vpn.stopVpnProfile(TEST_VPN_PKG);
fail("Expected SecurityException due to restricted user");
} catch (SecurityException expected) {
}
}
@Test
public void testStartOpAndFinishOpWillBeCalledWhenPlatformVpnIsOnAndOff() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
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);
verifyPlatformVpnIsActivated(TEST_VPN_PKG);
// Add a small delay to make sure that startOp is only called once.
verify(mAppOps, after(100).times(1)).startOp(
eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
eq(Process.myUid()),
eq(TEST_VPN_PKG),
eq(null) /* attributionTag */,
eq(null) /* message */);
// Check that the startOp is not called with OPSTR_ESTABLISH_VPN_SERVICE.
verify(mAppOps, never()).startOp(
eq(AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE),
eq(Process.myUid()),
eq(TEST_VPN_PKG),
eq(null) /* attributionTag */,
eq(null) /* message */);
vpn.stopVpnProfile(TEST_VPN_PKG);
verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
}
@Test
public void testStartOpWithSeamlessHandover() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN);
assertTrue(vpn.prepare(TEST_VPN_PKG, null, VpnManager.TYPE_VPN_SERVICE));
final VpnConfig config = new VpnConfig();
config.user = "VpnTest";
config.addresses.add(new LinkAddress("192.0.2.2/32"));
config.mtu = 1450;
final ResolveInfo resolveInfo = new ResolveInfo();
final ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.permission = BIND_VPN_SERVICE;
resolveInfo.serviceInfo = serviceInfo;
when(mPackageManager.resolveService(any(), anyInt())).thenReturn(resolveInfo);
when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
vpn.establish(config);
verify(mAppOps, times(1)).startOp(
eq(AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE),
eq(Process.myUid()),
eq(TEST_VPN_PKG),
eq(null) /* attributionTag */,
eq(null) /* message */);
// Call establish() twice with the same config, it should match seamless handover case and
// startOp() shouldn't be called again.
vpn.establish(config);
verify(mAppOps, times(1)).startOp(
eq(AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE),
eq(Process.myUid()),
eq(TEST_VPN_PKG),
eq(null) /* attributionTag */,
eq(null) /* message */);
}
private void verifyVpnManagerEvent(String sessionKey, String category, int errorClass,
int errorCode, VpnProfileState... profileState) {
final Context userContext =
mContext.createContextAsUser(UserHandle.of(PRIMARY_USER.id), 0 /* flags */);
final ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
final int verifyTimes = (profileState == null) ? 1 : profileState.length;
verify(userContext, times(verifyTimes)).startService(intentArgumentCaptor.capture());
for (int i = 0; i < verifyTimes; i++) {
final Intent intent = intentArgumentCaptor.getAllValues().get(i);
assertEquals(sessionKey, intent.getStringExtra(VpnManager.EXTRA_SESSION_KEY));
final Set<String> categories = intent.getCategories();
assertTrue(categories.contains(category));
assertEquals(errorClass,
intent.getIntExtra(VpnManager.EXTRA_ERROR_CLASS, -1 /* defaultValue */));
assertEquals(errorCode,
intent.getIntExtra(VpnManager.EXTRA_ERROR_CODE, -1 /* defaultValue */));
if (profileState != null) {
assertEquals(profileState[i], intent.getParcelableExtra(
VpnManager.EXTRA_VPN_PROFILE_STATE, VpnProfileState.class));
}
}
reset(userContext);
}
@Test
public void testVpnManagerEventForUserDeactivated() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
// For security reasons, Vpn#prepare() will check that oldPackage and newPackage are either
// null or the package of the caller. This test will call Vpn#prepare() to pretend the old
// VPN is replaced by a new one. But only Settings can change to some other packages, and
// this is checked with CONTROL_VPN so simulate holding CONTROL_VPN in order to pass the
// security checks.
doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
.thenReturn(mVpnProfile.encode());
// Test the case that the user deactivates the vpn in vpn app.
final String sessionKey1 = vpn.startVpnProfile(TEST_VPN_PKG);
verifyPlatformVpnIsActivated(TEST_VPN_PKG);
vpn.stopVpnProfile(TEST_VPN_PKG);
verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
reset(mDeviceIdleInternal);
// CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
// errorCode won't be set.
verifyVpnManagerEvent(sessionKey1, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
-1 /* errorClass */, -1 /* errorCode */, null /* profileState */);
reset(mAppOps);
// Test the case that the user chooses another vpn and the original one is replaced.
final String sessionKey2 = vpn.startVpnProfile(TEST_VPN_PKG);
verifyPlatformVpnIsActivated(TEST_VPN_PKG);
vpn.prepare(TEST_VPN_PKG, "com.new.vpn" /* newPackage */, TYPE_VPN_PLATFORM);
verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
reset(mDeviceIdleInternal);
// CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
// errorCode won't be set.
verifyVpnManagerEvent(sessionKey2, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
-1 /* errorClass */, -1 /* errorCode */, null /* profileState */);
}
@Test
public void testVpnManagerEventForAlwaysOnChanged() throws Exception {
assumeTrue(SdkLevel.isAtLeastT());
// Calling setAlwaysOnPackage() needs to hold CONTROL_VPN.
doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
final Vpn vpn = createVpn(PRIMARY_USER.id);
// Enable VPN always-on for PKGS[1].
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
null /* lockdownAllowlist */));
verifyPowerSaveTempWhitelistApp(PKGS[1]);
reset(mDeviceIdleInternal);
verifyVpnManagerEvent(null /* sessionKey */,
VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
-1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
// Enable VPN lockdown for PKGS[1].
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true /* lockdown */,
null /* lockdownAllowlist */));
verifyPowerSaveTempWhitelistApp(PKGS[1]);
reset(mDeviceIdleInternal);
verifyVpnManagerEvent(null /* sessionKey */,
VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
-1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
null /* sessionKey */, true /* alwaysOn */, true /* lockdown */));
// Disable VPN lockdown for PKGS[1].
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
null /* lockdownAllowlist */));
verifyPowerSaveTempWhitelistApp(PKGS[1]);
reset(mDeviceIdleInternal);
verifyVpnManagerEvent(null /* sessionKey */,
VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
-1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
// Disable VPN always-on.
assertTrue(vpn.setAlwaysOnPackage(null, false /* lockdown */,
null /* lockdownAllowlist */));
verifyPowerSaveTempWhitelistApp(PKGS[1]);
reset(mDeviceIdleInternal);
verifyVpnManagerEvent(null /* sessionKey */,
VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
-1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
null /* sessionKey */, false /* alwaysOn */, false /* lockdown */));
// Enable VPN always-on for PKGS[1] again.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
null /* lockdownAllowlist */));
verifyPowerSaveTempWhitelistApp(PKGS[1]);
reset(mDeviceIdleInternal);
verifyVpnManagerEvent(null /* sessionKey */,
VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
-1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
// Enable VPN always-on for PKGS[2].
assertTrue(vpn.setAlwaysOnPackage(PKGS[2], false /* lockdown */,
null /* lockdownAllowlist */));
verifyPowerSaveTempWhitelistApp(PKGS[2]);
reset(mDeviceIdleInternal);
// PKGS[1] is replaced with PKGS[2].
// Pass 2 VpnProfileState objects to verifyVpnManagerEvent(), the first one is sent to
// PKGS[1] to notify PKGS[1] that the VPN always-on is disabled, the second one is sent to
// PKGS[2] to notify PKGS[2] that the VPN always-on is enabled.
verifyVpnManagerEvent(null /* sessionKey */,
VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
-1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
null /* sessionKey */, false /* alwaysOn */, false /* lockdown */),
new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
}
@Test
public void testReconnectVpnManagerVpnWithAlwaysOnEnabled() throws Exception {
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);
verifyPlatformVpnIsActivated(TEST_VPN_PKG);
// Enable VPN always-on for TEST_VPN_PKG.
assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, false /* lockdown */,
null /* lockdownAllowlist */));
// Reset to verify next startVpnProfile.
reset(mAppOps);
vpn.stopVpnProfile(TEST_VPN_PKG);
// Reconnect the vpn with different package will cause exception.
assertThrows(SecurityException.class, () -> vpn.startVpnProfile(PKGS[0]));
// Reconnect the vpn again with the vpn always on package w/o exception.
vpn.startVpnProfile(TEST_VPN_PKG);
verifyPlatformVpnIsActivated(TEST_VPN_PKG);
}
@Test
public void testSetPackageAuthorizationVpnService() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_SERVICE));
verify(mAppOps)
.setMode(
eq(AppOpsManager.OPSTR_ACTIVATE_VPN),
eq(Process.myUid()),
eq(TEST_VPN_PKG),
eq(AppOpsManager.MODE_ALLOWED));
}
@Test
public void testSetPackageAuthorizationPlatformVpn() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, TYPE_VPN_PLATFORM));
verify(mAppOps)
.setMode(
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
eq(Process.myUid()),
eq(TEST_VPN_PKG),
eq(AppOpsManager.MODE_ALLOWED));
}
@Test
public void testSetPackageAuthorizationRevokeAuthorization() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_NONE));
verify(mAppOps)
.setMode(
eq(AppOpsManager.OPSTR_ACTIVATE_VPN),
eq(Process.myUid()),
eq(TEST_VPN_PKG),
eq(AppOpsManager.MODE_IGNORED));
verify(mAppOps)
.setMode(
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
eq(Process.myUid()),
eq(TEST_VPN_PKG),
eq(AppOpsManager.MODE_IGNORED));
}
private NetworkCallback triggerOnAvailableAndGetCallback() throws Exception {
final ArgumentCaptor<NetworkCallback> networkCallbackCaptor =
ArgumentCaptor.forClass(NetworkCallback.class);
verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
.registerSystemDefaultNetworkCallback(networkCallbackCaptor.capture(), any());
// onAvailable() will trigger onDefaultNetworkChanged(), so NetdUtils#setInterfaceUp will be
// invoked. Set the return value of INetd#interfaceGetCfg to prevent NullPointerException.
final InterfaceConfigurationParcel config = new InterfaceConfigurationParcel();
config.flags = new String[] {IF_STATE_DOWN};
when(mNetd.interfaceGetCfg(anyString())).thenReturn(config);
final NetworkCallback cb = networkCallbackCaptor.getValue();
cb.onAvailable(TEST_NETWORK);
return cb;
}
private void verifyInterfaceSetCfgWithFlags(String flag) throws Exception {
// Add a timeout for waiting for interfaceSetCfg to be called.
verify(mNetd, timeout(TEST_TIMEOUT_MS)).interfaceSetCfg(argThat(
config -> Arrays.asList(config.flags).contains(flag)));
}
private void doTestPlatformVpnWithException(IkeException exception,
String category, int errorType, int errorCode) throws Exception {
final ArgumentCaptor<IkeSessionCallback> captor =
ArgumentCaptor.forClass(IkeSessionCallback.class);
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
.thenReturn(mVpnProfile.encode());
final String sessionKey = vpn.startVpnProfile(TEST_VPN_PKG);
final NetworkCallback cb = triggerOnAvailableAndGetCallback();
verifyInterfaceSetCfgWithFlags(IF_STATE_UP);
// Wait for createIkeSession() to be called before proceeding in order to ensure consistent
// state
verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
.createIkeSession(any(), any(), any(), any(), captor.capture(), any());
reset(mIkev2SessionCreator);
final IkeSessionCallback ikeCb = captor.getValue();
ikeCb.onClosedWithException(exception);
verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
reset(mDeviceIdleInternal);
verifyVpnManagerEvent(sessionKey, category, errorType, errorCode, null /* profileState */);
if (errorType == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
.unregisterNetworkCallback(eq(cb));
} else if (errorType == VpnManager.ERROR_CLASS_RECOVERABLE) {
int retryIndex = 0;
final IkeSessionCallback ikeCb2 = verifyRetryAndGetNewIkeCb(retryIndex++);
ikeCb2.onClosedWithException(exception);
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);
final int errorCode = IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
when(exception.getErrorType()).thenReturn(errorCode);
doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
errorCode);
}
@Test
public void testStartPlatformVpnFailedWithRecoverableError() throws Exception {
final IkeProtocolException exception = mock(IkeProtocolException.class);
final int errorCode = IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
when(exception.getErrorType()).thenReturn(errorCode);
doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE, errorCode);
}
@Test
public void testStartPlatformVpnFailedWithUnknownHostException() throws Exception {
final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
final UnknownHostException unknownHostException = new UnknownHostException();
final int errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
when(exception.getCause()).thenReturn(unknownHostException);
doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
errorCode);
}
@Test
public void testStartPlatformVpnFailedWithIkeTimeoutException() throws Exception {
final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
final IkeTimeoutException ikeTimeoutException =
new IkeTimeoutException("IkeTimeoutException");
final int errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
when(exception.getCause()).thenReturn(ikeTimeoutException);
doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
errorCode);
}
@Test
public void testStartPlatformVpnFailedWithIkeNetworkLostException() throws Exception {
final IkeNetworkLostException exception = new IkeNetworkLostException(
new Network(100));
doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_LOST);
}
@Test
public void testStartPlatformVpnFailedWithIOException() throws Exception {
final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
final IOException ioException = new IOException();
final int errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
when(exception.getCause()).thenReturn(ioException);
doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
errorCode);
}
@Test
public void testStartPlatformVpnIllegalArgumentExceptionInSetup() throws Exception {
when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any()))
.thenThrow(new IllegalArgumentException());
final Vpn vpn = startLegacyVpn(createVpn(PRIMARY_USER.id), mVpnProfile);
final NetworkCallback cb = triggerOnAvailableAndGetCallback();
verifyInterfaceSetCfgWithFlags(IF_STATE_UP);
// Wait for createIkeSession() to be called before proceeding in order to ensure consistent
// state
verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb));
assertEquals(LegacyVpnInfo.STATE_FAILED, vpn.getLegacyVpnInfo().state);
}
@Test
public void testVpnManagerEventWillNotBeSentToSettingsVpn() throws Exception {
startLegacyVpn(createVpn(PRIMARY_USER.id), mVpnProfile);
triggerOnAvailableAndGetCallback();
verifyInterfaceSetCfgWithFlags(IF_STATE_UP);
final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
final IkeTimeoutException ikeTimeoutException =
new IkeTimeoutException("IkeTimeoutException");
when(exception.getCause()).thenReturn(ikeTimeoutException);
final ArgumentCaptor<IkeSessionCallback> captor =
ArgumentCaptor.forClass(IkeSessionCallback.class);
verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
.createIkeSession(any(), any(), any(), any(), captor.capture(), any());
final IkeSessionCallback ikeCb = captor.getValue();
ikeCb.onClosedWithException(exception);
final Context userContext =
mContext.createContextAsUser(UserHandle.of(PRIMARY_USER.id), 0 /* flags */);
verify(userContext, never()).startService(any());
}
private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) {
assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null));
verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
verify(mAppOps).setMode(
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG),
eq(AppOpsManager.MODE_ALLOWED));
verify(mSystemServices).settingsSecurePutStringForUser(
eq(Settings.Secure.ALWAYS_ON_VPN_APP), eq(TEST_VPN_PKG), eq(PRIMARY_USER.id));
verify(mSystemServices).settingsSecurePutIntForUser(
eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN), eq(lockdownEnabled ? 1 : 0),
eq(PRIMARY_USER.id));
verify(mSystemServices).settingsSecurePutStringForUser(
eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST), eq(""), eq(PRIMARY_USER.id));
}
@Test
public void testSetAndStartAlwaysOnVpn() throws Exception {
final Vpn vpn = createVpn(PRIMARY_USER.id);
setMockedUsers(PRIMARY_USER);
// UID checks must return a different UID; otherwise it'll be treated as already prepared.
final int uid = Process.myUid() + 1;
when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
.thenReturn(uid);
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
.thenReturn(mVpnProfile.encode());
setAndVerifyAlwaysOnPackage(vpn, uid, false);
assertTrue(vpn.startAlwaysOnVpn());
// TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
// a subsequent CL.
}
private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception {
setMockedUsers(PRIMARY_USER);
// Dummy egress interface
final LinkProperties lp = new LinkProperties();
lp.setInterfaceName(EGRESS_IFACE);
final RouteInfo defaultRoute = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
InetAddresses.parseNumericAddress("192.0.2.0"), EGRESS_IFACE);
lp.addRoute(defaultRoute);
vpn.startLegacyVpn(vpnProfile, EGRESS_NETWORK, lp);
return vpn;
}
private IkeSessionConnectionInfo createIkeConnectInfo() {
return new IkeSessionConnectionInfo(TEST_VPN_CLIENT_IP, TEST_VPN_SERVER_IP, TEST_NETWORK);
}
private IkeSessionConnectionInfo createIkeConnectInfo_2() {
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<IkeSessionCallback, ChildSessionCallback> verifyCreateIkeAndCaptureCbs()
throws Exception {
final ArgumentCaptor<IkeSessionCallback> ikeCbCaptor =
ArgumentCaptor.forClass(IkeSessionCallback.class);
final ArgumentCaptor<ChildSessionCallback> childCbCaptor =
ArgumentCaptor.forClass(ChildSessionCallback.class);
verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS)).createIkeSession(
any(), any(), any(), any(), ikeCbCaptor.capture(), childCbCaptor.capture());
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(mMockNetworkAgent).when(mTestDeps)
.newNetworkAgent(
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<IkeSessionCallback, ChildSessionCallback> 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<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class);
ArgumentCaptor<NetworkCapabilities> ncCaptor =
ArgumentCaptor.forClass(NetworkCapabilities.class);
verify(mTestDeps).newNetworkAgent(
any(), any(), anyString(), ncCaptor.capture(), lpCaptor.capture(),
any(), any(), any());
// Check LinkProperties
final LinkProperties lp = lpCaptor.getValue();
final List<RouteInfo> 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<LinkAddress> 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 {
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 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);
// Mock the MOBIKE procedure
vpnSnapShot.ikeCb.onIkeSessionConnectionInfoChanged(createIkeConnectInfo_2());
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(mMockNetworkAgent)
.doSetUnderlyingNetworks(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 to be prepared to capture callbacks of the second
// IKE session
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();
// Capture callbacks of the new IKE Session
final Pair<IkeSessionCallback, ChildSessionCallback> cbPair =
verifyCreateIkeAndCaptureCbs();
final IkeSessionCallback ikeCb = cbPair.first;
final ChildSessionCallback childCb = cbPair.second;
// Mock the IKE Session setup
ikeCb.onOpened(createIkeConfig(createIkeConnectInfo_2(), false /* isMobikeEnabled */));
childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_IN);
childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_OUT);
childCb.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(mMockNetworkAgent)
.doSetUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2));
vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
}
private void verifyHandlingNetworkLoss() throws Exception {
final ArgumentCaptor<LinkProperties> lpCaptor =
ArgumentCaptor.forClass(LinkProperties.class);
verify(mMockNetworkAgent).doSendLinkProperties(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 {
startRacoon("1.2.3.4", "1.2.3.4");
}
@Test
public void testStartRacoonHostname() throws Exception {
startRacoon("hostname", "5.6.7.8"); // address returned by deps.resolve
}
@Test
public void testStartPptp() throws Exception {
startPptp(true /* useMppe */);
}
@Test
public void testStartPptp_NoMppe() throws Exception {
startPptp(false /* useMppe */);
}
private void assertTransportInfoMatches(NetworkCapabilities nc, int type) {
assertNotNull(nc);
VpnTransportInfo ti = (VpnTransportInfo) nc.getTransportInfo();
assertNotNull(ti);
assertEquals(type, ti.getType());
}
private void startPptp(boolean useMppe) throws Exception {
final VpnProfile profile = new VpnProfile("testProfile" /* key */);
profile.type = VpnProfile.TYPE_PPTP;
profile.name = "testProfileName";
profile.username = "userName";
profile.password = "thePassword";
profile.server = "192.0.2.123";
profile.mppe = useMppe;
doReturn(new Network[] { new Network(101) }).when(mConnectivityManager).getAllNetworks();
doReturn(new Network(102)).when(mConnectivityManager).registerNetworkAgent(any(), any(),
any(), any(), any(), any(), anyInt());
final Vpn vpn = startLegacyVpn(createVpn(PRIMARY_USER.id), profile);
final TestDeps deps = (TestDeps) vpn.mDeps;
testAndCleanup(() -> {
final String[] mtpdArgs = deps.mtpdArgs.get(10, TimeUnit.SECONDS);
final String[] argsPrefix = new String[]{
EGRESS_IFACE, "pptp", profile.server, "1723", "name", profile.username,
"password", profile.password, "linkname", "vpn", "refuse-eap", "nodefaultroute",
"usepeerdns", "idle", "1800", "mtu", "1270", "mru", "1270"
};
assertArrayEquals(argsPrefix, Arrays.copyOf(mtpdArgs, argsPrefix.length));
if (useMppe) {
assertEquals(argsPrefix.length + 2, mtpdArgs.length);
assertEquals("+mppe", mtpdArgs[argsPrefix.length]);
assertEquals("-pap", mtpdArgs[argsPrefix.length + 1]);
} else {
assertEquals(argsPrefix.length + 1, mtpdArgs.length);
assertEquals("nomppe", mtpdArgs[argsPrefix.length]);
}
verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(),
any(), any(), any(), any(), anyInt());
}, () -> { // Cleanup
vpn.mVpnRunner.exitVpnRunner();
deps.getStateFile().delete(); // set to delete on exit, but this deletes it earlier
vpn.mVpnRunner.join(10_000); // wait for up to 10s for the runner to die and cleanup
});
}
public void startRacoon(final String serverAddr, final String expectedAddr)
throws Exception {
final ConditionVariable legacyRunnerReady = new ConditionVariable();
final VpnProfile profile = new VpnProfile("testProfile" /* key */);
profile.type = VpnProfile.TYPE_L2TP_IPSEC_PSK;
profile.name = "testProfileName";
profile.username = "userName";
profile.password = "thePassword";
profile.server = serverAddr;
profile.ipsecIdentifier = "id";
profile.ipsecSecret = "secret";
profile.l2tpSecret = "l2tpsecret";
when(mConnectivityManager.getAllNetworks())
.thenReturn(new Network[] { new Network(101) });
when(mConnectivityManager.registerNetworkAgent(any(), any(), any(), any(),
any(), any(), anyInt())).thenAnswer(invocation -> {
// The runner has registered an agent and is now ready.
legacyRunnerReady.open();
return new Network(102);
});
final Vpn vpn = startLegacyVpn(createVpn(PRIMARY_USER.id), profile);
final TestDeps deps = (TestDeps) vpn.mDeps;
try {
// udppsk and 1701 are the values for TYPE_L2TP_IPSEC_PSK
assertArrayEquals(
new String[] { EGRESS_IFACE, expectedAddr, "udppsk",
profile.ipsecIdentifier, profile.ipsecSecret, "1701" },
deps.racoonArgs.get(10, TimeUnit.SECONDS));
// literal values are hardcoded in Vpn.java for mtpd args
assertArrayEquals(
new String[] { EGRESS_IFACE, "l2tp", expectedAddr, "1701", profile.l2tpSecret,
"name", profile.username, "password", profile.password,
"linkname", "vpn", "refuse-eap", "nodefaultroute", "usepeerdns",
"idle", "1800", "mtu", "1270", "mru", "1270" },
deps.mtpdArgs.get(10, TimeUnit.SECONDS));
// Now wait for the runner to be ready before testing for the route.
ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class);
ArgumentCaptor<NetworkCapabilities> ncCaptor =
ArgumentCaptor.forClass(NetworkCapabilities.class);
verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(),
lpCaptor.capture(), ncCaptor.capture(), any(), any(), anyInt());
// In this test the expected address is always v4 so /32.
// Note that the interface needs to be specified because RouteInfo objects stored in
// LinkProperties objects always acquire the LinkProperties' interface.
final RouteInfo expectedRoute = new RouteInfo(new IpPrefix(expectedAddr + "/32"),
null, EGRESS_IFACE, RouteInfo.RTN_THROW);
final List<RouteInfo> actualRoutes = lpCaptor.getValue().getRoutes();
assertTrue("Expected throw route (" + expectedRoute + ") not found in " + actualRoutes,
actualRoutes.contains(expectedRoute));
assertTransportInfoMatches(ncCaptor.getValue(), VpnManager.TYPE_VPN_LEGACY);
} finally {
// Now interrupt the thread, unblock the runner and clean up.
vpn.mVpnRunner.exitVpnRunner();
deps.getStateFile().delete(); // set to delete on exit, but this deletes it earlier
vpn.mVpnRunner.join(10_000); // wait for up to 10s for the runner to die and cleanup
}
}
// Make it public and un-final so as to spy it
public class TestDeps extends Vpn.Dependencies {
public final CompletableFuture<String[]> racoonArgs = new CompletableFuture();
public final CompletableFuture<String[]> mtpdArgs = new CompletableFuture();
public final File mStateFile;
private final HashMap<String, Boolean> mRunningServices = new HashMap<>();
TestDeps() {
try {
mStateFile = File.createTempFile("vpnTest", ".tmp");
mStateFile.deleteOnExit();
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean isCallerSystem() {
return true;
}
@Override
public void startService(final String serviceName) {
mRunningServices.put(serviceName, true);
}
@Override
public void stopService(final String serviceName) {
mRunningServices.put(serviceName, false);
}
@Override
public boolean isServiceRunning(final String serviceName) {
return mRunningServices.getOrDefault(serviceName, false);
}
@Override
public boolean isServiceStopped(final String serviceName) {
return !isServiceRunning(serviceName);
}
@Override
public File getStateFile() {
return mStateFile;
}
@Override
public PendingIntent getIntentForStatusPanel(Context context) {
return null;
}
@Override
public void sendArgumentsToDaemon(
final String daemon, final LocalSocket socket, final String[] arguments,
final Vpn.RetryScheduler interruptChecker) throws IOException {
if ("racoon".equals(daemon)) {
racoonArgs.complete(arguments);
} else if ("mtpd".equals(daemon)) {
writeStateFile(arguments);
mtpdArgs.complete(arguments);
} else {
throw new UnsupportedOperationException("Unsupported daemon : " + daemon);
}
}
private void writeStateFile(final String[] arguments) throws IOException {
mStateFile.delete();
mStateFile.createNewFile();
mStateFile.deleteOnExit();
final BufferedWriter writer = new BufferedWriter(
new FileWriter(mStateFile, false /* append */));
writer.write(EGRESS_IFACE);
writer.write("\n");
// addresses
writer.write("10.0.0.1/24\n");
// routes
writer.write("192.168.6.0/24\n");
// dns servers
writer.write("192.168.6.1\n");
// search domains
writer.write("vpn.searchdomains.com\n");
// endpoint - intentionally empty
writer.write("\n");
writer.flush();
writer.close();
}
@Override
@NonNull
public InetAddress resolve(final String endpoint) {
try {
// If a numeric IP address, return it.
return InetAddress.parseNumericAddress(endpoint);
} catch (IllegalArgumentException e) {
// Otherwise, return some token IP to test for.
return InetAddress.parseNumericAddress("5.6.7.8");
}
}
@Override
public boolean isInterfacePresent(final Vpn vpn, final String iface) {
return true;
}
@Override
public ParcelFileDescriptor adoptFd(Vpn vpn, int mtu) {
return new ParcelFileDescriptor(new FileDescriptor());
}
@Override
public int jniCreate(Vpn vpn, int mtu) {
// Pick a random positive number as fd to return.
return 345;
}
@Override
public String jniGetName(Vpn vpn, int fd) {
return TEST_IFACE_NAME;
}
@Override
public int jniSetAddresses(Vpn vpn, String interfaze, String addresses) {
if (addresses == null) return 0;
// Return the number of addresses.
return addresses.split(" ").length;
}
@Override
public void setBlocking(FileDescriptor fd, boolean blocking) {}
@Override
public DeviceIdleInternal getDeviceIdleInternal() {
return mDeviceIdleInternal;
}
@Override
public long getNextRetryDelaySeconds(int retryCount) {
// Simply return retryCount as the delay seconds for retrying.
return retryCount;
}
@Override
public ScheduledThreadPoolExecutor newScheduledThreadPoolExecutor() {
return mExecutor;
}
}
/**
* Mock some methods of vpn object.
*/
private Vpn createVpn(@UserIdInt int userId) {
final Context asUserContext = mock(Context.class, AdditionalAnswers.delegatesTo(mContext));
doReturn(UserHandle.of(userId)).when(asUserContext).getUser();
when(mContext.createContextAsUser(eq(UserHandle.of(userId)), anyInt()))
.thenReturn(asUserContext);
final TestLooper testLooper = new TestLooper();
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")
));
return vpn;
}
/**
* Populate {@link #mUserManager} with a list of fake users.
*/
private void setMockedUsers(UserInfo... users) {
final Map<Integer, UserInfo> userMap = new ArrayMap<>();
for (UserInfo user : users) {
userMap.put(user.id, user);
}
/**
* @see UserManagerService#getUsers(boolean)
*/
doAnswer(invocation -> {
final ArrayList<UserInfo> result = new ArrayList<>(users.length);
for (UserInfo ui : users) {
if (ui.isEnabled() && !ui.partial) {
result.add(ui);
}
}
return result;
}).when(mUserManager).getAliveUsers();
doAnswer(invocation -> {
final int id = (int) invocation.getArguments()[0];
return userMap.get(id);
}).when(mUserManager).getUserInfo(anyInt());
}
/**
* Populate {@link #mPackageManager} with a fake packageName-to-UID mapping.
*/
private void setMockedPackages(final Map<String, Integer> packages) {
try {
doAnswer(invocation -> {
final String appName = (String) invocation.getArguments()[0];
final int userId = (int) invocation.getArguments()[1];
Integer appId = packages.get(appName);
if (appId == null) throw new PackageManager.NameNotFoundException(appName);
return UserHandle.getUid(userId, appId);
}).when(mPackageManager).getPackageUidAsUser(anyString(), anyInt());
} catch (Exception e) {
}
}
private void setMockedNetworks(final Map<Network, NetworkCapabilities> networks) {
doAnswer(invocation -> {
final Network network = (Network) invocation.getArguments()[0];
return networks.get(network);
}).when(mConnectivityManager).getNetworkCapabilities(any());
}
// Need multiple copies of this, but Java's Stream objects can't be reused or
// duplicated.
private Stream<String> publicIpV4Routes() {
return Stream.of(
"0.0.0.0/5", "8.0.0.0/7", "11.0.0.0/8", "12.0.0.0/6", "16.0.0.0/4",
"32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/3", "160.0.0.0/5", "168.0.0.0/6",
"172.0.0.0/12", "172.32.0.0/11", "172.64.0.0/10", "172.128.0.0/9",
"173.0.0.0/8", "174.0.0.0/7", "176.0.0.0/4", "192.0.0.0/9", "192.128.0.0/11",
"192.160.0.0/13", "192.169.0.0/16", "192.170.0.0/15", "192.172.0.0/14",
"192.176.0.0/12", "192.192.0.0/10", "193.0.0.0/8", "194.0.0.0/7",
"196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4");
}
private Stream<String> publicIpV6Routes() {
return Stream.of(
"::/1", "8000::/2", "c000::/3", "e000::/4", "f000::/5", "f800::/6",
"fe00::/8", "2605:ef80:e:af1d::/64");
}
}