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 excludeFromLockdown whether to exclude current package from lockdown.
106      */
waitForVpn(Context context, String packageName, boolean usable, boolean lockdown, boolean excludeFromLockdown)107     public static void waitForVpn(Context context, String packageName, boolean usable,
108             boolean lockdown, boolean excludeFromLockdown) {
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, excludeFromLockdown);
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 excludeFromLockdown)156     public static void setAlwaysOnVpn(
157             Context context, String packageName, boolean lockdown, boolean excludeFromLockdown)
158             throws PackageManager.NameNotFoundException {
159         final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
160         final Set<String> lockdownAllowlist;
161         if (lockdown) {
162             lockdownAllowlist = excludeFromLockdown ?
163                     Collections.singleton(context.getPackageName()) : Collections.emptySet();
164         } else {
165             lockdownAllowlist = null;
166         }
167         dpm.setAlwaysOnVpnPackage(
168                 ADMIN_RECEIVER_COMPONENT, packageName, lockdown, lockdownAllowlist);
169         assertEquals(packageName, dpm.getAlwaysOnVpnPackage(ADMIN_RECEIVER_COMPONENT));
170         assertEquals(lockdown, dpm.isAlwaysOnVpnLockdownEnabled(ADMIN_RECEIVER_COMPONENT));
171         assertEquals(lockdownAllowlist,
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