blob: b191de53ed1f59b45751335c4314a618d7f99bf8 [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.cts.deviceandprofileowner;
import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.TEST_ADDRESS;
import static com.android.cts.deviceandprofileowner.vpn.VpnTestHelper.VPN_PACKAGE;
import android.os.Bundle;
import android.os.UserManager;
import android.util.Log;
import com.android.compatibility.common.util.BlockingBroadcastReceiver;
import com.android.cts.deviceandprofileowner.vpn.VpnTestHelper;
import java.io.IOException;
import java.net.Socket;
/**
* Validates that a device owner or profile owner can set an always-on VPN without user action.
*
* A trivial VPN app is installed which reflects ping packets back to the sender. One ping packet is
* sent, received after its round-trip, and compared to the original packet to make sure nothing
* strange happened on the way through the VPN.
*
* All of the addresses in this test are fictional and any resemblance to real addresses is the
* result of a misconfigured network.
*/
public class AlwaysOnVpnTest extends BaseDeviceAdminTest {
private static final String TAG = "AlwaysOnVpnTest";
/** @see com.android.cts.vpnfirewall.ReflectorVpnService */
private static final String RESTRICTION_ALLOWED = "vpn.allowed";
private static final String RESTRICTION_DISALLOWED = "vpn.disallowed";
private static final String RESTRICTION_DONT_ESTABLISH = "vpn.dont_establish";
private static final String CONNECTIVITY_CHECK_HOST = "connectivitycheck.gstatic.com";
private static final int VPN_ON_START_TIMEOUT_MS = 5_000;
private static final long CONNECTIVITY_WAIT_TIME_NS = 30_000_000_000L;
private String mPackageName;
@Override
public void setUp() throws Exception {
super.setUp();
// Always-on is null by default.
assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
mPackageName = mContext.getPackageName();
}
@Override
public void tearDown() throws Exception {
mDevicePolicyManager.setAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT, null, false);
mDevicePolicyManager.setApplicationRestrictions(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE,
/* restrictions */ null);
super.tearDown();
}
public void testAlwaysOnVpn() throws Exception {
// test always-on is null by default
assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
VpnTestHelper.waitForVpn(mContext, VPN_PACKAGE,
/* usable */ true, /* lockdown */ true, /* allowlist */ false);
VpnTestHelper.checkPing(TEST_ADDRESS);
}
public void testDisallowConfigVpn() throws Exception {
mDevicePolicyManager.addUserRestriction(
ADMIN_RECEIVER_COMPONENT, UserManager.DISALLOW_CONFIG_VPN);
try {
testAlwaysOnVpn();
} finally {
// clear the user restriction
mDevicePolicyManager.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
UserManager.DISALLOW_CONFIG_VPN);
}
}
public void testAllowedApps() throws Exception {
final Bundle restrictions = new Bundle();
restrictions.putStringArray(RESTRICTION_ALLOWED, new String[] {mPackageName});
mDevicePolicyManager.setApplicationRestrictions(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE,
restrictions);
VpnTestHelper.waitForVpn(mContext, VPN_PACKAGE,
/* usable */ true, /* lockdown */ true, /* allowlist */ false);
assertTrue(VpnTestHelper.isNetworkVpn(mContext));
}
public void testDisallowedApps() throws Exception {
final Bundle restrictions = new Bundle();
restrictions.putStringArray(RESTRICTION_DISALLOWED, new String[] {mPackageName});
mDevicePolicyManager.setApplicationRestrictions(ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE,
restrictions);
VpnTestHelper.waitForVpn(mContext, VPN_PACKAGE,
/* usable */ false, /* lockdown */ true, /* allowlist */ false);
assertFalse(VpnTestHelper.isNetworkVpn(mContext));
}
// Tests that changes to lockdown allowlist are applied correctly.
public void testVpnLockdownUpdateAllowlist() throws Exception {
waitForConnectivity("VPN is off");
// VPN won't start.
final Bundle restrictions = new Bundle();
restrictions.putBoolean(RESTRICTION_DONT_ESTABLISH, true);
mDevicePolicyManager.setApplicationRestrictions(
ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE, restrictions);
// VPN service is started asynchronously, we need to wait for it to avoid stale service
// instance interfering with the next test.
final BlockingBroadcastReceiver receiver = VpnTestHelper.registerOnStartReceiver(mContext);
VpnTestHelper.setAlwaysOnVpn(
mContext, VPN_PACKAGE, /* lockdown */ false, /* allowlist */ false);
assertConnectivity(true, "VPN service not started, no lockdown");
assertNotNull(receiver.awaitForBroadcast(VPN_ON_START_TIMEOUT_MS));
VpnTestHelper.setAlwaysOnVpn(
mContext, VPN_PACKAGE, /* lockdown */ true, /* allowlist */ false);
// Wait for loss of connectivity instead of assertConnectivity(false)
// to mitigate flakiness due to asynchronicity.
waitForNoConnectivity("VPN in lockdown, service not started");
assertNotNull(receiver.awaitForBroadcast(VPN_ON_START_TIMEOUT_MS));
VpnTestHelper.setAlwaysOnVpn(
mContext, VPN_PACKAGE, /* lockdown */ true, /* allowlist */ true);
assertConnectivity(true, "VPN in lockdown, service not started, app allowlisted");
assertNotNull(receiver.awaitForBroadcast(VPN_ON_START_TIMEOUT_MS));
VpnTestHelper.setAlwaysOnVpn(
mContext, VPN_PACKAGE, /* lockdown */ true, /* allowlist */ false);
// Wait for loss of connectivity instead of assertConnectivity(false)
// to mitigate flakiness due to asynchronicity.
waitForNoConnectivity("VPN in lockdown, service not started");
assertNotNull(receiver.awaitForBroadcast(VPN_ON_START_TIMEOUT_MS));
receiver.unregisterQuietly();
}
// Tests that when VPN comes up, allowlisted app switches over to it.
public void testVpnLockdownAllowlistVpnComesUp() throws Exception {
waitForConnectivity("VPN is off");
// VPN won't start initially.
final Bundle restrictions = new Bundle();
restrictions.putBoolean(RESTRICTION_DONT_ESTABLISH, true);
mDevicePolicyManager.setApplicationRestrictions(
ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE, restrictions);
// VPN service is started asynchronously, we need to wait for it to avoid stale service
// instance interfering with the next test.
final BlockingBroadcastReceiver receiver = VpnTestHelper.registerOnStartReceiver(mContext);
VpnTestHelper.setAlwaysOnVpn(
mContext, VPN_PACKAGE, /* lockdown */ true, /* allowlist */ true);
assertConnectivity(true, "VPN in lockdown, service not started, app allowlisted");
assertNotNull(receiver.awaitForBroadcast(VPN_ON_START_TIMEOUT_MS));
// Make VPN workable again and restart.
mDevicePolicyManager.setApplicationRestrictions(
ADMIN_RECEIVER_COMPONENT, VPN_PACKAGE, null);
VpnTestHelper.waitForVpn(mContext, VPN_PACKAGE,
/* usable */ true, /* lockdown */ true, /* allowlist */ true);
// Now we should be on VPN.
VpnTestHelper.checkPing(TEST_ADDRESS);
receiver.unregisterQuietly();
}
public void testSetNonVpnAlwaysOn() throws Exception {
// Treat this CTS DPC as an non-vpn app, since it doesn't register
// android.net.VpnService intent filter in AndroidManifest.xml.
try {
mDevicePolicyManager.setAlwaysOnVpnPackage(
ADMIN_RECEIVER_COMPONENT, mPackageName, true);
fail("setAlwaysOnVpnPackage should not accept non-vpn package");
} catch (UnsupportedOperationException e) {
// success
}
assertNull(mDevicePolicyManager.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
}
private void waitForConnectivity(String message) throws InterruptedException {
long deadline = System.nanoTime() + CONNECTIVITY_WAIT_TIME_NS;
while (System.nanoTime() < deadline) {
try {
new Socket(CONNECTIVITY_CHECK_HOST, 80);
// Domain resolved, we have connectivity.
return;
} catch (IOException e) {
// Log.e(String, String, Throwable) will swallow UnknownHostException,
// so manually print it out here.
Log.e(TAG, "No connectivity yet: " + e.toString());
Thread.sleep(2000);
}
}
fail("Connectivity isn't available: " + message);
}
private void waitForNoConnectivity(String message) throws Exception {
long deadline = System.nanoTime() + CONNECTIVITY_WAIT_TIME_NS;
while (System.nanoTime() < deadline) {
try {
new Socket(CONNECTIVITY_CHECK_HOST, 80);
// Domain resolved, we have connectivity.
} catch (IOException e) {
// No connectivity
return;
}
Thread.sleep(2000);
}
fail("Connectivity still available after deadline: " + message);
}
private void assertConnectivity(boolean shouldHaveConnectivity, String message) {
try {
new Socket(CONNECTIVITY_CHECK_HOST, 80);
if (!shouldHaveConnectivity) {
fail("Connectivity available while not expected: " + message);
}
} catch (IOException e) {
if (shouldHaveConnectivity) {
Log.e(TAG, "Connectivity check failed", e);
fail("Connectivity isn't available while expected: " + message);
}
}
}
}