1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.cts.deviceandprofileowner.vpn; 18 19 import static android.system.OsConstants.AF_INET; 20 import static android.system.OsConstants.IPPROTO_ICMP; 21 import static android.system.OsConstants.POLLIN; 22 import static android.system.OsConstants.SOCK_DGRAM; 23 24 import static junit.framework.Assert.assertEquals; 25 import static junit.framework.Assert.assertNotNull; 26 import static junit.framework.Assert.assertTrue; 27 import static junit.framework.Assert.fail; 28 29 import android.annotation.TargetApi; 30 import android.app.admin.DevicePolicyManager; 31 import android.content.BroadcastReceiver; 32 import android.content.ComponentName; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.IntentFilter; 36 import android.content.pm.PackageManager; 37 import android.net.ConnectivityManager; 38 import android.net.Network; 39 import android.net.NetworkCapabilities; 40 import android.net.NetworkInfo; 41 import android.os.Build.VERSION_CODES; 42 import android.system.ErrnoException; 43 import android.system.Os; 44 import android.system.StructPollfd; 45 46 import com.android.compatibility.common.util.BlockingBroadcastReceiver; 47 import com.android.cts.deviceandprofileowner.BaseDeviceAdminTest; 48 49 import java.io.ByteArrayOutputStream; 50 import java.io.DataOutputStream; 51 import java.io.FileDescriptor; 52 import java.io.IOException; 53 import java.net.InetAddress; 54 import java.net.InetSocketAddress; 55 import java.util.Arrays; 56 import java.util.Collections; 57 import java.util.Set; 58 import java.util.concurrent.CountDownLatch; 59 import java.util.concurrent.TimeUnit; 60 import java.util.concurrent.atomic.AtomicBoolean; 61 62 /** 63 * Helper class to test vpn status 64 */ 65 @TargetApi(VERSION_CODES.N) 66 public class VpnTestHelper { 67 public static final String VPN_PACKAGE = "com.android.cts.vpnfirewall"; 68 private static final String MY_PACKAGE = "com.android.cts.deviceandprofileowner"; 69 // Broadcast by ReflectorVpnService when the interface is up. 70 private static final String ACTION_VPN_IS_UP = VPN_PACKAGE + ".VPN_IS_UP"; 71 // Broadcast by ReflectorVpnService receives onStartCommand and queried app restrictions. 72 private static final String ACTION_VPN_ON_START = VPN_PACKAGE + ".VPN_ON_START"; 73 74 // IP address reserved for documentation by rfc5737 75 public static final String TEST_ADDRESS = "192.0.2.4"; 76 77 private static final String EXTRA_ALWAYS_ON = "always-on"; 78 private static final String EXTRA_LOCKDOWN = "lockdown"; 79 80 // HACK (TODO issue 31585407) to wait for the network to actually be usable 81 private static final int NETWORK_SETTLE_GRACE_MS = 200; 82 83 private static final int SOCKET_TIMEOUT_MS = 5000; 84 private static final int ICMP_ECHO_REQUEST = 0x08; 85 private static final int ICMP_ECHO_REPLY = 0x00; 86 private static final int NETWORK_TIMEOUT_MS = 5000; 87 private static final ComponentName ADMIN_RECEIVER_COMPONENT = 88 BaseDeviceAdminTest.ADMIN_RECEIVER_COMPONENT; 89 registerOnStartReceiver(Context context)90 public static BlockingBroadcastReceiver registerOnStartReceiver(Context context) { 91 final BlockingBroadcastReceiver receiver = 92 new BlockingBroadcastReceiver(context, ACTION_VPN_ON_START); 93 receiver.register(); 94 return receiver; 95 } 96 97 /** 98 * Wait for a VPN app to establish VPN. 99 * 100 * @param context Caller's context. 101 * @param packageName {@code null} if waiting for the existing VPN to connect. Otherwise we set 102 * this package as the new always-on VPN app and wait for it to connect. 103 * @param lockdown Disallow connectivity while VPN is down. 104 * @param usable Whether the resulting VPN tunnel is expected to be usable. 105 * @param whitelist whether to whitelist current package from lockdown. 106 */ waitForVpn(Context context, String packageName, boolean usable, boolean lockdown, boolean whitelist)107 public static void waitForVpn(Context context, String packageName, boolean usable, 108 boolean lockdown, boolean whitelist) { 109 final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); 110 if (packageName == null) { 111 assertNotNull(dpm.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT)); 112 } 113 114 ConnectivityManager cm = context.getSystemService(ConnectivityManager.class); 115 final CountDownLatch vpnLatch = new CountDownLatch(1); 116 final IntentFilter intentFilter = new IntentFilter(ACTION_VPN_IS_UP); 117 final AtomicBoolean isAlwaysOn = new AtomicBoolean(); 118 final AtomicBoolean isLockdown = new AtomicBoolean(); 119 final BroadcastReceiver receiver = new BroadcastReceiver() { 120 @Override 121 public void onReceive(final Context context, final Intent intent) { 122 if (!intent.getPackage().equals(MY_PACKAGE)) return; 123 isAlwaysOn.set(intent.getBooleanExtra(EXTRA_ALWAYS_ON, false)); 124 isLockdown.set(intent.getBooleanExtra(EXTRA_LOCKDOWN, !lockdown)); 125 vpnLatch.countDown(); 126 context.unregisterReceiver(this); 127 } 128 }; 129 context.registerReceiver(receiver, intentFilter); 130 131 try { 132 if (packageName != null) { 133 setAlwaysOnVpn(context, packageName, lockdown, whitelist); 134 } 135 if (!vpnLatch.await(NETWORK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { 136 if (!isNetworkVpn(context)) { 137 fail("Took too long waiting to establish a VPN-backed connection"); 138 } 139 } else { 140 assertTrue("Wrong VpnService#isAlwaysOn()", isAlwaysOn.get()); 141 assertEquals("Wrong VpnService#isLockdownEnabled()", lockdown, isLockdown.get()); 142 } 143 Thread.sleep(NETWORK_SETTLE_GRACE_MS); 144 } catch (InterruptedException | PackageManager.NameNotFoundException e) { 145 fail("Failed while waiting for VPN: " + e); 146 } 147 148 // Do we have a network? 149 NetworkInfo vpnInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_VPN); 150 assertNotNull(vpnInfo); 151 152 // Is it usable? 153 assertEquals(usable, vpnInfo.isConnected()); 154 } 155 setAlwaysOnVpn( Context context, String packageName, boolean lockdown, boolean whitelist)156 public static void setAlwaysOnVpn( 157 Context context, String packageName, boolean lockdown, boolean whitelist) 158 throws PackageManager.NameNotFoundException { 159 final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); 160 final Set<String> lockdownWhitelist; 161 if (lockdown) { 162 lockdownWhitelist = whitelist ? 163 Collections.singleton(context.getPackageName()) : Collections.emptySet(); 164 } else { 165 lockdownWhitelist = null; 166 } 167 dpm.setAlwaysOnVpnPackage( 168 ADMIN_RECEIVER_COMPONENT, packageName, lockdown, lockdownWhitelist); 169 assertEquals(packageName, dpm.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT)); 170 assertEquals(lockdown, dpm.isAlwaysOnVpnLockdownEnabled(ADMIN_RECEIVER_COMPONENT)); 171 assertEquals(lockdownWhitelist, 172 dpm.getAlwaysOnVpnLockdownWhitelist(ADMIN_RECEIVER_COMPONENT)); 173 } 174 isNetworkVpn(Context context)175 public static boolean isNetworkVpn(Context context) { 176 ConnectivityManager connectivityManager = 177 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 178 Network network = connectivityManager.getActiveNetwork(); 179 NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); 180 return capabilities != null && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN); 181 } 182 checkPing(String host)183 public static void checkPing(String host) throws ErrnoException, IOException { 184 FileDescriptor socket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); 185 186 // Create an ICMP message 187 final int identifier = 0x7E57; 188 final String message = "test packet"; 189 byte[] echo = createIcmpMessage(ICMP_ECHO_REQUEST, 0x00, identifier, 0, message.getBytes()); 190 191 // Send the echo packet. 192 int port = new InetSocketAddress(0).getPort(); 193 Os.connect(socket, InetAddress.getByName(host), port); 194 Os.write(socket, echo, 0, echo.length); 195 196 // Expect a reply. 197 StructPollfd pollfd = new StructPollfd(); 198 pollfd.events = (short) POLLIN; 199 pollfd.fd = socket; 200 int ret = Os.poll(new StructPollfd[] { pollfd }, SOCKET_TIMEOUT_MS); 201 assertEquals("Expected reply after sending ping", 1, ret); 202 203 byte[] reply = new byte[echo.length]; 204 int read = Os.read(socket, reply, 0, echo.length); 205 assertEquals(echo.length, read); 206 207 // Ignore control type differences since echo=8, reply=0. 208 assertEquals(echo[0], ICMP_ECHO_REQUEST); 209 assertEquals(reply[0], ICMP_ECHO_REPLY); 210 echo[0] = 0; 211 reply[0] = 0; 212 213 // Fix ICMP ID which kernel will have changed on the way out. 214 InetSocketAddress local = (InetSocketAddress) Os.getsockname(socket); 215 port = local.getPort(); 216 echo[4] = (byte) ((port >> 8) & 0xFF); 217 echo[5] = (byte) (port & 0xFF); 218 219 // Ignore checksum differences since the types are not supposed to match. 220 echo[2] = echo[3] = 0; 221 reply[2] = reply[3] = 0; 222 223 assertTrue("Packet contents do not match." 224 + "\nEcho packet: " + Arrays.toString(echo) 225 + "\nReply packet: " + Arrays.toString(reply), Arrays.equals(echo, reply)); 226 227 // Close socket if the test pass. Otherwise, any error will kill the process. 228 Os.close(socket); 229 } 230 tryPosixConnect(String host)231 public static void tryPosixConnect(String host) throws ErrnoException, IOException { 232 FileDescriptor socket = null; 233 try { 234 socket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); 235 int port = new InetSocketAddress(0).getPort(); 236 Os.connect(socket, InetAddress.getByName(host), port); 237 } finally { 238 if (socket != null) { 239 Os.close(socket); 240 } 241 } 242 } 243 createIcmpMessage(int type, int code, int extra1, int extra2, byte[] data)244 private static byte[] createIcmpMessage(int type, int code, int extra1, int extra2, 245 byte[] data) throws IOException { 246 ByteArrayOutputStream output = new ByteArrayOutputStream(); 247 DataOutputStream stream = new DataOutputStream(output); 248 stream.writeByte(type); 249 stream.writeByte(code); 250 stream.writeShort(/* checksum */ 0); 251 stream.writeShort((short) extra1); 252 stream.writeShort((short) extra2); 253 stream.write(data, 0, data.length); 254 return output.toByteArray(); 255 } 256 } 257