Home | History | Annotate | Download | only in hostside
      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 
    115     private boolean supportedHardware() {
    116         final PackageManager pm = getInstrumentation().getContext().getPackageManager();
    117         return !pm.hasSystemFeature("android.hardware.type.watch");
    118     }
    119 
    120     @Override
    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
    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 
    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.
    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 
    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 
    290     private static void closeQuietly(Closeable c) {
    291         if (c != null) {
    292             try {
    293                 c.close();
    294             } catch (IOException e) {
    295             }
    296         }
    297     }
    298 
    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.
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    900     private boolean isNetworkMetered(Network network) {
    901         NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
    902         return !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
    903     }
    904 
    905     private void assertVpnTransportContains(Network underlyingNetwork) {
    906         int[] transports = mCM.getNetworkCapabilities(underlyingNetwork).getTransportTypes();
    907         assertVpnTransportContains(transports);
    908     }
    909 
    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 
    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 
    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 
    938         public ProxyChangeBroadcastReceiver() {
    939             super(VpnTest.this.getInstrumentation().getContext(), Proxy.PROXY_CHANGE_ACTION);
    940             received = false;
    941         }
    942 
    943         @Override
    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