Home | History | Annotate | Download | only in vpn
      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 
     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      */
    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 
    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 
    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 
    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 
    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 
    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