• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.net.hostside;
18 
19 import static android.os.Process.INVALID_UID;
20 import static android.system.OsConstants.*;
21 
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.PackageManager;
26 import android.net.ConnectivityManager;
27 import android.net.ConnectivityManager.NetworkCallback;
28 import android.net.LinkProperties;
29 import android.net.Network;
30 import android.net.NetworkCapabilities;
31 import android.net.NetworkRequest;
32 import android.net.Proxy;
33 import android.net.ProxyInfo;
34 import android.net.VpnService;
35 import android.net.wifi.WifiManager;
36 import android.os.ParcelFileDescriptor;
37 import android.os.Process;
38 import android.os.SystemProperties;
39 import android.support.test.uiautomator.UiDevice;
40 import android.support.test.uiautomator.UiObject;
41 import android.support.test.uiautomator.UiSelector;
42 import android.system.ErrnoException;
43 import android.system.Os;
44 import android.system.OsConstants;
45 import android.system.StructPollfd;
46 import android.test.InstrumentationTestCase;
47 import android.test.MoreAsserts;
48 import android.text.TextUtils;
49 import android.util.Log;
50 
51 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
52 
53 import java.io.Closeable;
54 import java.io.FileDescriptor;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.io.OutputStream;
58 import java.net.DatagramPacket;
59 import java.net.DatagramSocket;
60 import java.net.Inet6Address;
61 import java.net.InetAddress;
62 import java.net.InetSocketAddress;
63 import java.net.ServerSocket;
64 import java.net.Socket;
65 import java.nio.charset.StandardCharsets;
66 import java.util.ArrayList;
67 import java.util.Random;
68 import java.util.concurrent.CountDownLatch;
69 import java.util.concurrent.TimeUnit;
70 
71 /**
72  * Tests for the VpnService API.
73  *
74  * These tests establish a VPN via the VpnService API, and have the service reflect the packets back
75  * to the device without causing any network traffic. This allows testing the local VPN data path
76  * without a network connection or a VPN server.
77  *
78  * Note: in Lollipop, VPN functionality relies on kernel support for UID-based routing. If these
79  * tests fail, it may be due to the lack of kernel support. The necessary patches can be
80  * cherry-picked from the Android common kernel trees:
81  *
82  * android-3.10:
83  *   https://android-review.googlesource.com/#/c/99220/
84  *   https://android-review.googlesource.com/#/c/100545/
85  *
86  * android-3.4:
87  *   https://android-review.googlesource.com/#/c/99225/
88  *   https://android-review.googlesource.com/#/c/100557/
89  *
90  * To ensure that the kernel has the required commits, run the kernel unit
91  * tests described at:
92  *
93  *   https://source.android.com/devices/tech/config/kernel_network_tests.html
94  *
95  */
96 public class VpnTest extends InstrumentationTestCase {
97 
98     public static String TAG = "VpnTest";
99     public static int TIMEOUT_MS = 3 * 1000;
100     public static int SOCKET_TIMEOUT_MS = 100;
101     public static String TEST_HOST = "connectivitycheck.gstatic.com";
102 
103     private UiDevice mDevice;
104     private MyActivity mActivity;
105     private String mPackageName;
106     private ConnectivityManager mCM;
107     private WifiManager mWifiManager;
108     private RemoteSocketFactoryClient mRemoteSocketFactoryClient;
109 
110     Network mNetwork;
111     NetworkCallback mCallback;
112     final Object mLock = new Object();
113     final Object mLockShutdown = new Object();
114 
supportedHardware()115     private boolean supportedHardware() {
116         final PackageManager pm = getInstrumentation().getContext().getPackageManager();
117         return !pm.hasSystemFeature("android.hardware.type.watch");
118     }
119 
120     @Override
setUp()121     public void setUp() throws Exception {
122         super.setUp();
123 
124         mNetwork = null;
125         mCallback = null;
126 
127         mDevice = UiDevice.getInstance(getInstrumentation());
128         mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
129                 MyActivity.class, null);
130         mPackageName = mActivity.getPackageName();
131         mCM = (ConnectivityManager) mActivity.getSystemService(Context.CONNECTIVITY_SERVICE);
132         mWifiManager = (WifiManager) mActivity.getSystemService(Context.WIFI_SERVICE);
133         mRemoteSocketFactoryClient = new RemoteSocketFactoryClient(mActivity);
134         mRemoteSocketFactoryClient.bind();
135         mDevice.waitForIdle();
136     }
137 
138     @Override
tearDown()139     public void tearDown() throws Exception {
140         mRemoteSocketFactoryClient.unbind();
141         if (mCallback != null) {
142             mCM.unregisterNetworkCallback(mCallback);
143         }
144         Log.i(TAG, "Stopping VPN");
145         stopVpn();
146         mActivity.finish();
147         super.tearDown();
148     }
149 
prepareVpn()150     private void prepareVpn() throws Exception {
151         final int REQUEST_ID = 42;
152 
153         // Attempt to prepare.
154         Log.i(TAG, "Preparing VPN");
155         Intent intent = VpnService.prepare(mActivity);
156 
157         if (intent != null) {
158             // Start the confirmation dialog and click OK.
159             mActivity.startActivityForResult(intent, REQUEST_ID);
160             mDevice.waitForIdle();
161 
162             String packageName = intent.getComponent().getPackageName();
163             String resourceIdRegex = "android:id/button1$|button_start_vpn";
164             final UiObject okButton = new UiObject(new UiSelector()
165                     .className("android.widget.Button")
166                     .packageName(packageName)
167                     .resourceIdMatches(resourceIdRegex));
168             if (okButton.waitForExists(TIMEOUT_MS) == false) {
169                 mActivity.finishActivity(REQUEST_ID);
170                 fail("VpnService.prepare returned an Intent for '" + intent.getComponent() + "' " +
171                      "to display the VPN confirmation dialog, but this test could not find the " +
172                      "button to allow the VPN application to connect. Please ensure that the "  +
173                      "component displays a button with a resource ID matching the regexp: '" +
174                      resourceIdRegex + "'.");
175             }
176 
177             // Click the button and wait for RESULT_OK.
178             okButton.click();
179             try {
180                 int result = mActivity.getResult(TIMEOUT_MS);
181                 if (result != MyActivity.RESULT_OK) {
182                     fail("The VPN confirmation dialog did not return RESULT_OK when clicking on " +
183                          "the button matching the regular expression '" + resourceIdRegex +
184                          "' of " + intent.getComponent() + "'. Please ensure that clicking on " +
185                          "that button allows the VPN application to connect. " +
186                          "Return value: " + result);
187                 }
188             } catch (InterruptedException e) {
189                 fail("VPN confirmation dialog did not return after " + TIMEOUT_MS + "ms");
190             }
191 
192             // Now we should be prepared.
193             intent = VpnService.prepare(mActivity);
194             if (intent != null) {
195                 fail("VpnService.prepare returned non-null even after the VPN dialog " +
196                      intent.getComponent() + "returned RESULT_OK.");
197             }
198         }
199     }
200 
201     // TODO: Consider replacing arguments with a Builder.
startVpn( String[] addresses, String[] routes, String allowedApplications, String disallowedApplications, @Nullable ProxyInfo proxyInfo, @Nullable ArrayList<Network> underlyingNetworks, boolean isAlwaysMetered)202     private void startVpn(
203         String[] addresses, String[] routes, String allowedApplications,
204         String disallowedApplications, @Nullable ProxyInfo proxyInfo,
205         @Nullable ArrayList<Network> underlyingNetworks, boolean isAlwaysMetered) throws Exception {
206         prepareVpn();
207 
208         // Register a callback so we will be notified when our VPN comes up.
209         final NetworkRequest request = new NetworkRequest.Builder()
210                 .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
211                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
212                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
213                 .build();
214         mCallback = new NetworkCallback() {
215             public void onAvailable(Network network) {
216                 synchronized (mLock) {
217                     Log.i(TAG, "Got available callback for network=" + network);
218                     mNetwork = network;
219                     mLock.notify();
220                 }
221             }
222         };
223         mCM.registerNetworkCallback(request, mCallback);  // Unregistered in tearDown.
224 
225         // Start the service and wait up for TIMEOUT_MS ms for the VPN to come up.
226         Intent intent = new Intent(mActivity, MyVpnService.class)
227                 .putExtra(mPackageName + ".cmd", "connect")
228                 .putExtra(mPackageName + ".addresses", TextUtils.join(",", addresses))
229                 .putExtra(mPackageName + ".routes", TextUtils.join(",", routes))
230                 .putExtra(mPackageName + ".allowedapplications", allowedApplications)
231                 .putExtra(mPackageName + ".disallowedapplications", disallowedApplications)
232                 .putExtra(mPackageName + ".httpProxy", proxyInfo)
233                 .putParcelableArrayListExtra(
234                     mPackageName + ".underlyingNetworks", underlyingNetworks)
235                 .putExtra(mPackageName + ".isAlwaysMetered", isAlwaysMetered);
236 
237         mActivity.startService(intent);
238         synchronized (mLock) {
239             if (mNetwork == null) {
240                  Log.i(TAG, "bf mLock");
241                  mLock.wait(TIMEOUT_MS);
242                  Log.i(TAG, "af mLock");
243             }
244         }
245 
246         if (mNetwork == null) {
247             fail("VPN did not become available after " + TIMEOUT_MS + "ms");
248         }
249 
250         // Unfortunately, when the available callback fires, the VPN UID ranges are not yet
251         // configured. Give the system some time to do so. http://b/18436087 .
252         try { Thread.sleep(3000); } catch(InterruptedException e) {}
253     }
254 
stopVpn()255     private void stopVpn() {
256         // Register a callback so we will be notified when our VPN comes up.
257         final NetworkRequest request = new NetworkRequest.Builder()
258                 .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
259                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
260                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
261                 .build();
262         mCallback = new NetworkCallback() {
263             public void onLost(Network network) {
264                 synchronized (mLockShutdown) {
265                     Log.i(TAG, "Got lost callback for network=" + network
266                             + ",mNetwork = " + mNetwork);
267                     if( mNetwork == network){
268                         mLockShutdown.notify();
269                     }
270                 }
271             }
272        };
273         mCM.registerNetworkCallback(request, mCallback);  // Unregistered in tearDown.
274         // Simply calling mActivity.stopService() won't stop the service, because the system binds
275         // to the service for the purpose of sending it a revoke command if another VPN comes up,
276         // and stopping a bound service has no effect. Instead, "start" the service again with an
277         // Intent that tells it to disconnect.
278         Intent intent = new Intent(mActivity, MyVpnService.class)
279                 .putExtra(mPackageName + ".cmd", "disconnect");
280         mActivity.startService(intent);
281         synchronized (mLockShutdown) {
282             try {
283                  Log.i(TAG, "bf mLockShutdown");
284                  mLockShutdown.wait(TIMEOUT_MS);
285                  Log.i(TAG, "af mLockShutdown");
286             } catch(InterruptedException e) {}
287         }
288     }
289 
closeQuietly(Closeable c)290     private static void closeQuietly(Closeable c) {
291         if (c != null) {
292             try {
293                 c.close();
294             } catch (IOException e) {
295             }
296         }
297     }
298 
checkPing(String to)299     private static void checkPing(String to) throws IOException, ErrnoException {
300         InetAddress address = InetAddress.getByName(to);
301         FileDescriptor s;
302         final int LENGTH = 64;
303         byte[] packet = new byte[LENGTH];
304         byte[] header;
305 
306         // Construct a ping packet.
307         Random random = new Random();
308         random.nextBytes(packet);
309         if (address instanceof Inet6Address) {
310             s = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
311             header = new byte[] { (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
312         } else {
313             // Note that this doesn't actually work due to http://b/18558481 .
314             s = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
315             header = new byte[] { (byte) 0x08, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
316         }
317         System.arraycopy(header, 0, packet, 0, header.length);
318 
319         // Send the packet.
320         int port = random.nextInt(65534) + 1;
321         Os.connect(s, address, port);
322         Os.write(s, packet, 0, packet.length);
323 
324         // Expect a reply.
325         StructPollfd pollfd = new StructPollfd();
326         pollfd.events = (short) POLLIN;  // "error: possible loss of precision"
327         pollfd.fd = s;
328         int ret = Os.poll(new StructPollfd[] { pollfd }, SOCKET_TIMEOUT_MS);
329         assertEquals("Expected reply after sending ping", 1, ret);
330 
331         byte[] reply = new byte[LENGTH];
332         int read = Os.read(s, reply, 0, LENGTH);
333         assertEquals(LENGTH, read);
334 
335         // Find out what the kernel set the ICMP ID to.
336         InetSocketAddress local = (InetSocketAddress) Os.getsockname(s);
337         port = local.getPort();
338         packet[4] = (byte) ((port >> 8) & 0xff);
339         packet[5] = (byte) (port & 0xff);
340 
341         // Check the contents.
342         if (packet[0] == (byte) 0x80) {
343             packet[0] = (byte) 0x81;
344         } else {
345             packet[0] = 0;
346         }
347         // Zero out the checksum in the reply so it matches the uninitialized checksum in packet.
348         reply[2] = reply[3] = 0;
349         MoreAsserts.assertEquals(packet, reply);
350     }
351 
352     // Writes data to out and checks that it appears identically on in.
writeAndCheckData( OutputStream out, InputStream in, byte[] data)353     private static void writeAndCheckData(
354             OutputStream out, InputStream in, byte[] data) throws IOException {
355         out.write(data, 0, data.length);
356         out.flush();
357 
358         byte[] read = new byte[data.length];
359         int bytesRead = 0, totalRead = 0;
360         do {
361             bytesRead = in.read(read, totalRead, read.length - totalRead);
362             totalRead += bytesRead;
363         } while (bytesRead >= 0 && totalRead < data.length);
364         assertEquals(totalRead, data.length);
365         MoreAsserts.assertEquals(data, read);
366     }
367 
checkTcpReflection(String to, String expectedFrom)368     private void checkTcpReflection(String to, String expectedFrom) throws IOException {
369         // Exercise TCP over the VPN by "connecting to ourselves". We open a server socket and a
370         // client socket, and connect the client socket to a remote host, with the port of the
371         // server socket. The PacketReflector reflects the packets, changing the source addresses
372         // but not the ports, so our client socket is connected to our server socket, though both
373         // sockets think their peers are on the "remote" IP address.
374 
375         // Open a listening socket.
376         ServerSocket listen = new ServerSocket(0, 10, InetAddress.getByName("::"));
377 
378         // Connect the client socket to it.
379         InetAddress toAddr = InetAddress.getByName(to);
380         Socket client = new Socket();
381         try {
382             client.connect(new InetSocketAddress(toAddr, listen.getLocalPort()), SOCKET_TIMEOUT_MS);
383             if (expectedFrom == null) {
384                 closeQuietly(listen);
385                 closeQuietly(client);
386                 fail("Expected connection to fail, but it succeeded.");
387             }
388         } catch (IOException e) {
389             if (expectedFrom != null) {
390                 closeQuietly(listen);
391                 fail("Expected connection to succeed, but it failed.");
392             } else {
393                 // We expected the connection to fail, and it did, so there's nothing more to test.
394                 return;
395             }
396         }
397 
398         // The connection succeeded, and we expected it to succeed. Send some data; if things are
399         // working, the data will be sent to the VPN, reflected by the PacketReflector, and arrive
400         // at our server socket. For good measure, send some data in the other direction.
401         Socket server = null;
402         try {
403             // Accept the connection on the server side.
404             listen.setSoTimeout(SOCKET_TIMEOUT_MS);
405             server = listen.accept();
406             checkConnectionOwnerUidTcp(client);
407             checkConnectionOwnerUidTcp(server);
408             // Check that the source and peer addresses are as expected.
409             assertEquals(expectedFrom, client.getLocalAddress().getHostAddress());
410             assertEquals(expectedFrom, server.getLocalAddress().getHostAddress());
411             assertEquals(
412                     new InetSocketAddress(toAddr, client.getLocalPort()),
413                     server.getRemoteSocketAddress());
414             assertEquals(
415                     new InetSocketAddress(toAddr, server.getLocalPort()),
416                     client.getRemoteSocketAddress());
417 
418             // Now write some data.
419             final int LENGTH = 32768;
420             byte[] data = new byte[LENGTH];
421             new Random().nextBytes(data);
422 
423             // Make sure our writes don't block or time out, because we're single-threaded and can't
424             // read and write at the same time.
425             server.setReceiveBufferSize(LENGTH * 2);
426             client.setSendBufferSize(LENGTH * 2);
427             client.setSoTimeout(SOCKET_TIMEOUT_MS);
428             server.setSoTimeout(SOCKET_TIMEOUT_MS);
429 
430             // Send some data from client to server, then from server to client.
431             writeAndCheckData(client.getOutputStream(), server.getInputStream(), data);
432             writeAndCheckData(server.getOutputStream(), client.getInputStream(), data);
433         } finally {
434             closeQuietly(listen);
435             closeQuietly(client);
436             closeQuietly(server);
437         }
438     }
439 
checkConnectionOwnerUidUdp(DatagramSocket s, boolean expectSuccess)440     private void checkConnectionOwnerUidUdp(DatagramSocket s, boolean expectSuccess) {
441         final int expectedUid = expectSuccess ? Process.myUid() : INVALID_UID;
442         InetSocketAddress loc = new InetSocketAddress(s.getLocalAddress(), s.getLocalPort());
443         InetSocketAddress rem = new InetSocketAddress(s.getInetAddress(), s.getPort());
444         int uid = mCM.getConnectionOwnerUid(OsConstants.IPPROTO_UDP, loc, rem);
445         assertEquals(expectedUid, uid);
446     }
447 
checkConnectionOwnerUidTcp(Socket s)448     private void checkConnectionOwnerUidTcp(Socket s) {
449         final int expectedUid = Process.myUid();
450         InetSocketAddress loc = new InetSocketAddress(s.getLocalAddress(), s.getLocalPort());
451         InetSocketAddress rem = new InetSocketAddress(s.getInetAddress(), s.getPort());
452         int uid = mCM.getConnectionOwnerUid(OsConstants.IPPROTO_TCP, loc, rem);
453         assertEquals(expectedUid, uid);
454     }
455 
checkUdpEcho(String to, String expectedFrom)456     private void checkUdpEcho(String to, String expectedFrom) throws IOException {
457         DatagramSocket s;
458         InetAddress address = InetAddress.getByName(to);
459         if (address instanceof Inet6Address) {  // http://b/18094870
460             s = new DatagramSocket(0, InetAddress.getByName("::"));
461         } else {
462             s = new DatagramSocket();
463         }
464         s.setSoTimeout(SOCKET_TIMEOUT_MS);
465 
466         Random random = new Random();
467         byte[] data = new byte[random.nextInt(1650)];
468         random.nextBytes(data);
469         DatagramPacket p = new DatagramPacket(data, data.length);
470         s.connect(address, 7);
471 
472         if (expectedFrom != null) {
473             assertEquals("Unexpected source address: ",
474                          expectedFrom, s.getLocalAddress().getHostAddress());
475         }
476 
477         try {
478             if (expectedFrom != null) {
479                 s.send(p);
480                 checkConnectionOwnerUidUdp(s, true);
481                 s.receive(p);
482                 MoreAsserts.assertEquals(data, p.getData());
483             } else {
484                 try {
485                     s.send(p);
486                     s.receive(p);
487                     fail("Received unexpected reply");
488                 } catch (IOException expected) {
489                     checkConnectionOwnerUidUdp(s, false);
490                 }
491             }
492         } finally {
493             s.close();
494         }
495     }
496 
checkTrafficOnVpn()497     private void checkTrafficOnVpn() throws Exception {
498         checkUdpEcho("192.0.2.251", "192.0.2.2");
499         checkUdpEcho("2001:db8:dead:beef::f00", "2001:db8:1:2::ffe");
500         checkPing("2001:db8:dead:beef::f00");
501         checkTcpReflection("192.0.2.252", "192.0.2.2");
502         checkTcpReflection("2001:db8:dead:beef::f00", "2001:db8:1:2::ffe");
503     }
504 
checkNoTrafficOnVpn()505     private void checkNoTrafficOnVpn() throws Exception {
506         checkUdpEcho("192.0.2.251", null);
507         checkUdpEcho("2001:db8:dead:beef::f00", null);
508         checkTcpReflection("192.0.2.252", null);
509         checkTcpReflection("2001:db8:dead:beef::f00", null);
510     }
511 
openSocketFd(String host, int port, int timeoutMs)512     private FileDescriptor openSocketFd(String host, int port, int timeoutMs) throws Exception {
513         Socket s = new Socket(host, port);
514         s.setSoTimeout(timeoutMs);
515         // Dup the filedescriptor so ParcelFileDescriptor's finalizer doesn't garbage collect it
516         // and cause our fd to become invalid. http://b/35927643 .
517         FileDescriptor fd = Os.dup(ParcelFileDescriptor.fromSocket(s).getFileDescriptor());
518         s.close();
519         return fd;
520     }
521 
openSocketFdInOtherApp( String host, int port, int timeoutMs)522     private FileDescriptor openSocketFdInOtherApp(
523             String host, int port, int timeoutMs) throws Exception {
524         Log.d(TAG, String.format("Creating test socket in UID=%d, my UID=%d",
525                 mRemoteSocketFactoryClient.getUid(), Os.getuid()));
526         FileDescriptor fd = mRemoteSocketFactoryClient.openSocketFd(host, port, TIMEOUT_MS);
527         return fd;
528     }
529 
sendRequest(FileDescriptor fd, String host)530     private void sendRequest(FileDescriptor fd, String host) throws Exception {
531         String request = "GET /generate_204 HTTP/1.1\r\n" +
532                 "Host: " + host + "\r\n" +
533                 "Connection: keep-alive\r\n\r\n";
534         byte[] requestBytes = request.getBytes(StandardCharsets.UTF_8);
535         int ret = Os.write(fd, requestBytes, 0, requestBytes.length);
536         Log.d(TAG, "Wrote " + ret + "bytes");
537 
538         String expected = "HTTP/1.1 204 No Content\r\n";
539         byte[] response = new byte[expected.length()];
540         Os.read(fd, response, 0, response.length);
541 
542         String actual = new String(response, StandardCharsets.UTF_8);
543         assertEquals(expected, actual);
544         Log.d(TAG, "Got response: " + actual);
545     }
546 
assertSocketStillOpen(FileDescriptor fd, String host)547     private void assertSocketStillOpen(FileDescriptor fd, String host) throws Exception {
548         try {
549             assertTrue(fd.valid());
550             sendRequest(fd, host);
551             assertTrue(fd.valid());
552         } finally {
553             Os.close(fd);
554         }
555     }
556 
assertSocketClosed(FileDescriptor fd, String host)557     private void assertSocketClosed(FileDescriptor fd, String host) throws Exception {
558         try {
559             assertTrue(fd.valid());
560             sendRequest(fd, host);
561             fail("Socket opened before VPN connects should be closed when VPN connects");
562         } catch (ErrnoException expected) {
563             assertEquals(ECONNABORTED, expected.errno);
564             assertTrue(fd.valid());
565         } finally {
566             Os.close(fd);
567         }
568     }
569 
testDefault()570     public void testDefault() throws Exception {
571         if (!supportedHardware()) return;
572         // If adb TCP port opened, this test may running by adb over network.
573         // All of socket would be destroyed in this test. So this test don't
574         // support adb over network, see b/119382723.
575         if (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1
576                 || SystemProperties.getInt("service.adb.tcp.port", -1) > -1) {
577             Log.i(TAG, "adb is running over the network, so skip this test");
578             return;
579         }
580 
581         final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(
582                 getInstrumentation().getTargetContext(), MyVpnService.ACTION_ESTABLISHED);
583         receiver.register();
584 
585         FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
586 
587         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
588                  new String[] {"0.0.0.0/0", "::/0"},
589                  "", "", null, null /* underlyingNetworks */, false /* isAlwaysMetered */);
590 
591         final Intent intent = receiver.awaitForBroadcast(TimeUnit.MINUTES.toMillis(1));
592         assertNotNull("Failed to receive broadcast from VPN service", intent);
593         assertFalse("Wrong VpnService#isAlwaysOn",
594                 intent.getBooleanExtra(MyVpnService.EXTRA_ALWAYS_ON, true));
595         assertFalse("Wrong VpnService#isLockdownEnabled",
596                 intent.getBooleanExtra(MyVpnService.EXTRA_LOCKDOWN_ENABLED, true));
597 
598         assertSocketClosed(fd, TEST_HOST);
599 
600         checkTrafficOnVpn();
601         receiver.unregisterQuietly();
602     }
603 
testAppAllowed()604     public void testAppAllowed() throws Exception {
605         if (!supportedHardware()) return;
606 
607         FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
608 
609         // Shell app must not be put in here or it would kill the ADB-over-network use case
610         String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
611         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
612                  new String[] {"192.0.2.0/24", "2001:db8::/32"},
613                  allowedApps, "", null, null /* underlyingNetworks */, false /* isAlwaysMetered */);
614 
615         assertSocketClosed(fd, TEST_HOST);
616 
617         checkTrafficOnVpn();
618     }
619 
testAppDisallowed()620     public void testAppDisallowed() throws Exception {
621         if (!supportedHardware()) return;
622 
623         FileDescriptor localFd = openSocketFd(TEST_HOST, 80, TIMEOUT_MS);
624         FileDescriptor remoteFd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
625 
626         String disallowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
627         // If adb TCP port opened, this test may running by adb over TCP.
628         // Add com.android.shell appllication into blacklist to exclude adb socket for VPN test,
629         // see b/119382723.
630         // Note: The test don't support running adb over network for root device
631         disallowedApps = disallowedApps + ",com.android.shell";
632         Log.i(TAG, "Append shell app to disallowedApps: " + disallowedApps);
633         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
634                  new String[] {"192.0.2.0/24", "2001:db8::/32"},
635                  "", disallowedApps, null, null /* underlyingNetworks */,
636                  false /* isAlwaysMetered */);
637 
638         assertSocketStillOpen(localFd, TEST_HOST);
639         assertSocketStillOpen(remoteFd, TEST_HOST);
640 
641         checkNoTrafficOnVpn();
642     }
643 
testGetConnectionOwnerUidSecurity()644     public void testGetConnectionOwnerUidSecurity() throws Exception {
645         if (!supportedHardware()) return;
646 
647         DatagramSocket s;
648         InetAddress address = InetAddress.getByName("localhost");
649         s = new DatagramSocket();
650         s.setSoTimeout(SOCKET_TIMEOUT_MS);
651         s.connect(address, 7);
652         InetSocketAddress loc = new InetSocketAddress(s.getLocalAddress(), s.getLocalPort());
653         InetSocketAddress rem = new InetSocketAddress(s.getInetAddress(), s.getPort());
654         try {
655             int uid = mCM.getConnectionOwnerUid(OsConstants.IPPROTO_TCP, loc, rem);
656             fail("Only an active VPN app may call this API.");
657         } catch (SecurityException expected) {
658             return;
659         }
660     }
661 
testSetProxy()662     public void testSetProxy() throws  Exception {
663         if (!supportedHardware()) return;
664         ProxyInfo initialProxy = mCM.getDefaultProxy();
665         // Receiver for the proxy change broadcast.
666         BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
667         proxyBroadcastReceiver.register();
668 
669         String allowedApps = mPackageName;
670         ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("10.0.0.1", 8888);
671         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
672                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "",
673                 testProxyInfo, null /* underlyingNetworks */, false /* isAlwaysMetered */);
674 
675         // Check that the proxy change broadcast is received
676         try {
677             assertNotNull("No proxy change was broadcast when VPN is connected.",
678                     proxyBroadcastReceiver.awaitForBroadcast());
679         } finally {
680             proxyBroadcastReceiver.unregisterQuietly();
681         }
682 
683         // Proxy is set correctly in network and in link properties.
684         assertNetworkHasExpectedProxy(testProxyInfo, mNetwork);
685         assertDefaultProxy(testProxyInfo);
686 
687         proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
688         proxyBroadcastReceiver.register();
689         stopVpn();
690         try {
691             assertNotNull("No proxy change was broadcast when VPN was disconnected.",
692                     proxyBroadcastReceiver.awaitForBroadcast());
693         } finally {
694             proxyBroadcastReceiver.unregisterQuietly();
695         }
696 
697         // After disconnecting from VPN, the proxy settings are the ones of the initial network.
698         assertDefaultProxy(initialProxy);
699     }
700 
testSetProxyDisallowedApps()701     public void testSetProxyDisallowedApps() throws Exception {
702         if (!supportedHardware()) return;
703         ProxyInfo initialProxy = mCM.getDefaultProxy();
704 
705         // If adb TCP port opened, this test may running by adb over TCP.
706         // Add com.android.shell appllication into blacklist to exclude adb socket for VPN test,
707         // see b/119382723.
708         // Note: The test don't support running adb over network for root device
709         String disallowedApps = mPackageName + ",com.android.shell";
710         ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("10.0.0.1", 8888);
711         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
712                 new String[] {"0.0.0.0/0", "::/0"}, "", disallowedApps,
713                 testProxyInfo, null /* underlyingNetworks */, false /* isAlwaysMetered */);
714 
715         // The disallowed app does has the proxy configs of the default network.
716         assertNetworkHasExpectedProxy(initialProxy, mCM.getActiveNetwork());
717         assertDefaultProxy(initialProxy);
718     }
719 
testNoProxy()720     public void testNoProxy() throws Exception {
721         if (!supportedHardware()) return;
722         ProxyInfo initialProxy = mCM.getDefaultProxy();
723         BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
724         proxyBroadcastReceiver.register();
725         String allowedApps = mPackageName;
726         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
727                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
728                 null /* underlyingNetworks */, false /* isAlwaysMetered */);
729 
730         try {
731             assertNotNull("No proxy change was broadcast.",
732                     proxyBroadcastReceiver.awaitForBroadcast());
733         } finally {
734             proxyBroadcastReceiver.unregisterQuietly();
735         }
736 
737         // The VPN network has no proxy set.
738         assertNetworkHasExpectedProxy(null, mNetwork);
739 
740         proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
741         proxyBroadcastReceiver.register();
742         stopVpn();
743         try {
744             assertNotNull("No proxy change was broadcast.",
745                     proxyBroadcastReceiver.awaitForBroadcast());
746         } finally {
747             proxyBroadcastReceiver.unregisterQuietly();
748         }
749         // After disconnecting from VPN, the proxy settings are the ones of the initial network.
750         assertDefaultProxy(initialProxy);
751         assertNetworkHasExpectedProxy(initialProxy, mCM.getActiveNetwork());
752     }
753 
testBindToNetworkWithProxy()754     public void testBindToNetworkWithProxy() throws Exception {
755         if (!supportedHardware()) return;
756         String allowedApps = mPackageName;
757         Network initialNetwork = mCM.getActiveNetwork();
758         ProxyInfo initialProxy = mCM.getDefaultProxy();
759         ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("10.0.0.1", 8888);
760         // Receiver for the proxy change broadcast.
761         BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
762         proxyBroadcastReceiver.register();
763         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
764                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "",
765                 testProxyInfo, null /* underlyingNetworks */, false /* isAlwaysMetered */);
766 
767         assertDefaultProxy(testProxyInfo);
768         mCM.bindProcessToNetwork(initialNetwork);
769         try {
770             assertNotNull("No proxy change was broadcast.",
771                 proxyBroadcastReceiver.awaitForBroadcast());
772         } finally {
773             proxyBroadcastReceiver.unregisterQuietly();
774         }
775         assertDefaultProxy(initialProxy);
776     }
777 
testVpnMeterednessWithNoUnderlyingNetwork()778     public void testVpnMeterednessWithNoUnderlyingNetwork() throws Exception {
779         if (!supportedHardware()) {
780             return;
781         }
782         // VPN is not routing any traffic i.e. its underlying networks is an empty array.
783         ArrayList<Network> underlyingNetworks = new ArrayList<>();
784         String allowedApps = mPackageName;
785 
786         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
787                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
788                 underlyingNetworks, false /* isAlwaysMetered */);
789 
790         // VPN should now be the active network.
791         assertEquals(mNetwork, mCM.getActiveNetwork());
792         assertVpnTransportContains(NetworkCapabilities.TRANSPORT_VPN);
793         // VPN with no underlying networks should be metered by default.
794         assertTrue(isNetworkMetered(mNetwork));
795         assertTrue(mCM.isActiveNetworkMetered());
796     }
797 
testVpnMeterednessWithNullUnderlyingNetwork()798     public void testVpnMeterednessWithNullUnderlyingNetwork() throws Exception {
799         if (!supportedHardware()) {
800             return;
801         }
802         Network underlyingNetwork = mCM.getActiveNetwork();
803         if (underlyingNetwork == null) {
804             Log.i(TAG, "testVpnMeterednessWithNullUnderlyingNetwork cannot execute"
805                     + " unless there is an active network");
806             return;
807         }
808         // VPN tracks platform default.
809         ArrayList<Network> underlyingNetworks = null;
810         String allowedApps = mPackageName;
811 
812         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
813                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
814                 underlyingNetworks, false /*isAlwaysMetered */);
815 
816         // Ensure VPN transports contains underlying network's transports.
817         assertVpnTransportContains(underlyingNetwork);
818         // Its meteredness should be same as that of underlying network.
819         assertEquals(isNetworkMetered(underlyingNetwork), isNetworkMetered(mNetwork));
820         // Meteredness based on VPN capabilities and CM#isActiveNetworkMetered should be in sync.
821         assertEquals(isNetworkMetered(mNetwork), mCM.isActiveNetworkMetered());
822     }
823 
testVpnMeterednessWithNonNullUnderlyingNetwork()824     public void testVpnMeterednessWithNonNullUnderlyingNetwork() throws Exception {
825         if (!supportedHardware()) {
826             return;
827         }
828         Network underlyingNetwork = mCM.getActiveNetwork();
829         if (underlyingNetwork == null) {
830             Log.i(TAG, "testVpnMeterednessWithNonNullUnderlyingNetwork cannot execute"
831                     + " unless there is an active network");
832             return;
833         }
834         // VPN explicitly declares WiFi to be its underlying network.
835         ArrayList<Network> underlyingNetworks = new ArrayList<>(1);
836         underlyingNetworks.add(underlyingNetwork);
837         String allowedApps = mPackageName;
838 
839         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
840                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
841                 underlyingNetworks, false /* isAlwaysMetered */);
842 
843         // Ensure VPN transports contains underlying network's transports.
844         assertVpnTransportContains(underlyingNetwork);
845         // Its meteredness should be same as that of underlying network.
846         assertEquals(isNetworkMetered(underlyingNetwork), isNetworkMetered(mNetwork));
847         // Meteredness based on VPN capabilities and CM#isActiveNetworkMetered should be in sync.
848         assertEquals(isNetworkMetered(mNetwork), mCM.isActiveNetworkMetered());
849     }
850 
testAlwaysMeteredVpnWithNullUnderlyingNetwork()851     public void testAlwaysMeteredVpnWithNullUnderlyingNetwork() throws Exception {
852         if (!supportedHardware()) {
853             return;
854         }
855         Network underlyingNetwork = mCM.getActiveNetwork();
856         if (underlyingNetwork == null) {
857             Log.i(TAG, "testAlwaysMeteredVpnWithNullUnderlyingNetwork cannot execute"
858                     + " unless there is an active network");
859             return;
860         }
861         // VPN tracks platform default.
862         ArrayList<Network> underlyingNetworks = null;
863         String allowedApps = mPackageName;
864         boolean isAlwaysMetered = true;
865 
866         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
867                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
868                 underlyingNetworks, isAlwaysMetered);
869 
870         // VPN's meteredness does not depend on underlying network since it is always metered.
871         assertTrue(isNetworkMetered(mNetwork));
872         assertTrue(mCM.isActiveNetworkMetered());
873     }
874 
testAlwaysMeteredVpnWithNonNullUnderlyingNetwork()875     public void testAlwaysMeteredVpnWithNonNullUnderlyingNetwork() throws Exception {
876         if (!supportedHardware()) {
877             return;
878         }
879         Network underlyingNetwork = mCM.getActiveNetwork();
880         if (underlyingNetwork == null) {
881             Log.i(TAG, "testAlwaysMeteredVpnWithNonNullUnderlyingNetwork cannot execute"
882                     + " unless there is an active network");
883             return;
884         }
885         // VPN explicitly declares its underlying network.
886         ArrayList<Network> underlyingNetworks = new ArrayList<>(1);
887         underlyingNetworks.add(underlyingNetwork);
888         String allowedApps = mPackageName;
889         boolean isAlwaysMetered = true;
890 
891         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
892                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
893                 underlyingNetworks, isAlwaysMetered);
894 
895         // VPN's meteredness does not depend on underlying network since it is always metered.
896         assertTrue(isNetworkMetered(mNetwork));
897         assertTrue(mCM.isActiveNetworkMetered());
898     }
899 
isNetworkMetered(Network network)900     private boolean isNetworkMetered(Network network) {
901         NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
902         return !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
903     }
904 
assertVpnTransportContains(Network underlyingNetwork)905     private void assertVpnTransportContains(Network underlyingNetwork) {
906         int[] transports = mCM.getNetworkCapabilities(underlyingNetwork).getTransportTypes();
907         assertVpnTransportContains(transports);
908     }
909 
assertVpnTransportContains(int... transports)910     private void assertVpnTransportContains(int... transports) {
911         NetworkCapabilities vpnCaps = mCM.getNetworkCapabilities(mNetwork);
912         for (int transport : transports) {
913             assertTrue(vpnCaps.hasTransport(transport));
914         }
915     }
916 
assertDefaultProxy(ProxyInfo expected)917     private void assertDefaultProxy(ProxyInfo expected) {
918         assertEquals("Incorrect proxy config.", expected, mCM.getDefaultProxy());
919         String expectedHost = expected == null ? null : expected.getHost();
920         String expectedPort = expected == null ? null : String.valueOf(expected.getPort());
921         assertEquals("Incorrect proxy host system property.", expectedHost,
922             System.getProperty("http.proxyHost"));
923         assertEquals("Incorrect proxy port system property.", expectedPort,
924             System.getProperty("http.proxyPort"));
925     }
926 
assertNetworkHasExpectedProxy(ProxyInfo expected, Network network)927     private void assertNetworkHasExpectedProxy(ProxyInfo expected, Network network) {
928         LinkProperties lp = mCM.getLinkProperties(network);
929         assertNotNull("The network link properties object is null.", lp);
930         assertEquals("Incorrect proxy config.", expected, lp.getHttpProxy());
931 
932         assertEquals(expected, mCM.getProxyForNetwork(network));
933     }
934 
935     class ProxyChangeBroadcastReceiver extends BlockingBroadcastReceiver {
936         private boolean received;
937 
ProxyChangeBroadcastReceiver()938         public ProxyChangeBroadcastReceiver() {
939             super(VpnTest.this.getInstrumentation().getContext(), Proxy.PROXY_CHANGE_ACTION);
940             received = false;
941         }
942 
943         @Override
onReceive(Context context, Intent intent)944         public void onReceive(Context context, Intent intent) {
945             if (!received) {
946                 // Do not call onReceive() more than once.
947                 super.onReceive(context, intent);
948             }
949             received = true;
950         }
951     }
952 }
953