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.Manifest.permission.NETWORK_SETTINGS;
20 import static android.net.ConnectivityManager.TYPE_VPN;
21 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
22 import static android.os.Process.INVALID_UID;
23 import static android.system.OsConstants.AF_INET;
24 import static android.system.OsConstants.AF_INET6;
25 import static android.system.OsConstants.ECONNABORTED;
26 import static android.system.OsConstants.IPPROTO_ICMP;
27 import static android.system.OsConstants.IPPROTO_ICMPV6;
28 import static android.system.OsConstants.IPPROTO_TCP;
29 import static android.system.OsConstants.POLLIN;
30 import static android.system.OsConstants.SOCK_DGRAM;
31 import static android.test.MoreAsserts.assertNotEqual;
32 
33 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
34 
35 import android.annotation.Nullable;
36 import android.app.DownloadManager;
37 import android.app.DownloadManager.Query;
38 import android.app.DownloadManager.Request;
39 import android.content.BroadcastReceiver;
40 import android.content.ContentResolver;
41 import android.content.Context;
42 import android.content.Intent;
43 import android.content.IntentFilter;
44 import android.content.pm.PackageManager;
45 import android.database.Cursor;
46 import android.net.ConnectivityManager;
47 import android.net.ConnectivityManager.NetworkCallback;
48 import android.net.LinkProperties;
49 import android.net.Network;
50 import android.net.NetworkCapabilities;
51 import android.net.NetworkRequest;
52 import android.net.Proxy;
53 import android.net.ProxyInfo;
54 import android.net.TransportInfo;
55 import android.net.Uri;
56 import android.net.VpnManager;
57 import android.net.VpnService;
58 import android.net.VpnTransportInfo;
59 import android.net.wifi.WifiManager;
60 import android.os.Handler;
61 import android.os.Looper;
62 import android.os.ParcelFileDescriptor;
63 import android.os.Process;
64 import android.os.SystemProperties;
65 import android.os.UserHandle;
66 import android.provider.Settings;
67 import android.support.test.uiautomator.UiDevice;
68 import android.support.test.uiautomator.UiObject;
69 import android.support.test.uiautomator.UiSelector;
70 import android.system.ErrnoException;
71 import android.system.Os;
72 import android.system.OsConstants;
73 import android.system.StructPollfd;
74 import android.test.InstrumentationTestCase;
75 import android.test.MoreAsserts;
76 import android.text.TextUtils;
77 import android.util.Log;
78 
79 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
80 import com.android.modules.utils.build.SdkLevel;
81 import com.android.testutils.TestableNetworkCallback;
82 
83 import java.io.Closeable;
84 import java.io.FileDescriptor;
85 import java.io.IOException;
86 import java.io.InputStream;
87 import java.io.OutputStream;
88 import java.net.DatagramPacket;
89 import java.net.DatagramSocket;
90 import java.net.Inet6Address;
91 import java.net.InetAddress;
92 import java.net.InetSocketAddress;
93 import java.net.ServerSocket;
94 import java.net.Socket;
95 import java.net.UnknownHostException;
96 import java.nio.charset.StandardCharsets;
97 import java.util.ArrayList;
98 import java.util.List;
99 import java.util.Objects;
100 import java.util.Random;
101 import java.util.concurrent.CompletableFuture;
102 import java.util.concurrent.CountDownLatch;
103 import java.util.concurrent.TimeUnit;
104 
105 /**
106  * Tests for the VpnService API.
107  *
108  * These tests establish a VPN via the VpnService API, and have the service reflect the packets back
109  * to the device without causing any network traffic. This allows testing the local VPN data path
110  * without a network connection or a VPN server.
111  *
112  * Note: in Lollipop, VPN functionality relies on kernel support for UID-based routing. If these
113  * tests fail, it may be due to the lack of kernel support. The necessary patches can be
114  * cherry-picked from the Android common kernel trees:
115  *
116  * android-3.10:
117  *   https://android-review.googlesource.com/#/c/99220/
118  *   https://android-review.googlesource.com/#/c/100545/
119  *
120  * android-3.4:
121  *   https://android-review.googlesource.com/#/c/99225/
122  *   https://android-review.googlesource.com/#/c/100557/
123  *
124  * To ensure that the kernel has the required commits, run the kernel unit
125  * tests described at:
126  *
127  *   https://source.android.com/devices/tech/config/kernel_network_tests.html
128  *
129  */
130 public class VpnTest extends InstrumentationTestCase {
131 
132     // These are neither public nor @TestApi.
133     // TODO: add them to @TestApi.
134     private static final String PRIVATE_DNS_MODE_SETTING = "private_dns_mode";
135     private static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
136     private static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
137     private static final String PRIVATE_DNS_SPECIFIER_SETTING = "private_dns_specifier";
138 
139     public static String TAG = "VpnTest";
140     public static int TIMEOUT_MS = 3 * 1000;
141     public static int SOCKET_TIMEOUT_MS = 100;
142     public static String TEST_HOST = "connectivitycheck.gstatic.com";
143 
144     private UiDevice mDevice;
145     private MyActivity mActivity;
146     private String mPackageName;
147     private ConnectivityManager mCM;
148     private WifiManager mWifiManager;
149     private RemoteSocketFactoryClient mRemoteSocketFactoryClient;
150 
151     Network mNetwork;
152     NetworkCallback mCallback;
153     final Object mLock = new Object();
154     final Object mLockShutdown = new Object();
155 
156     private String mOldPrivateDnsMode;
157     private String mOldPrivateDnsSpecifier;
158 
supportedHardware()159     private boolean supportedHardware() {
160         final PackageManager pm = getInstrumentation().getContext().getPackageManager();
161         return !pm.hasSystemFeature("android.hardware.type.watch");
162     }
163 
164     @Override
setUp()165     public void setUp() throws Exception {
166         super.setUp();
167 
168         mNetwork = null;
169         mCallback = null;
170         storePrivateDnsSetting();
171 
172         mDevice = UiDevice.getInstance(getInstrumentation());
173         mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
174                 MyActivity.class, null);
175         mPackageName = mActivity.getPackageName();
176         mCM = (ConnectivityManager) mActivity.getSystemService(Context.CONNECTIVITY_SERVICE);
177         mWifiManager = (WifiManager) mActivity.getSystemService(Context.WIFI_SERVICE);
178         mRemoteSocketFactoryClient = new RemoteSocketFactoryClient(mActivity);
179         mRemoteSocketFactoryClient.bind();
180         mDevice.waitForIdle();
181     }
182 
183     @Override
tearDown()184     public void tearDown() throws Exception {
185         restorePrivateDnsSetting();
186         mRemoteSocketFactoryClient.unbind();
187         if (mCallback != null) {
188             mCM.unregisterNetworkCallback(mCallback);
189         }
190         Log.i(TAG, "Stopping VPN");
191         stopVpn();
192         mActivity.finish();
193         super.tearDown();
194     }
195 
prepareVpn()196     private void prepareVpn() throws Exception {
197         final int REQUEST_ID = 42;
198 
199         // Attempt to prepare.
200         Log.i(TAG, "Preparing VPN");
201         Intent intent = VpnService.prepare(mActivity);
202 
203         if (intent != null) {
204             // Start the confirmation dialog and click OK.
205             mActivity.startActivityForResult(intent, REQUEST_ID);
206             mDevice.waitForIdle();
207 
208             String packageName = intent.getComponent().getPackageName();
209             String resourceIdRegex = "android:id/button1$|button_start_vpn";
210             final UiObject okButton = new UiObject(new UiSelector()
211                     .className("android.widget.Button")
212                     .packageName(packageName)
213                     .resourceIdMatches(resourceIdRegex));
214             if (okButton.waitForExists(TIMEOUT_MS) == false) {
215                 mActivity.finishActivity(REQUEST_ID);
216                 fail("VpnService.prepare returned an Intent for '" + intent.getComponent() + "' " +
217                      "to display the VPN confirmation dialog, but this test could not find the " +
218                      "button to allow the VPN application to connect. Please ensure that the "  +
219                      "component displays a button with a resource ID matching the regexp: '" +
220                      resourceIdRegex + "'.");
221             }
222 
223             // Click the button and wait for RESULT_OK.
224             okButton.click();
225             try {
226                 int result = mActivity.getResult(TIMEOUT_MS);
227                 if (result != MyActivity.RESULT_OK) {
228                     fail("The VPN confirmation dialog did not return RESULT_OK when clicking on " +
229                          "the button matching the regular expression '" + resourceIdRegex +
230                          "' of " + intent.getComponent() + "'. Please ensure that clicking on " +
231                          "that button allows the VPN application to connect. " +
232                          "Return value: " + result);
233                 }
234             } catch (InterruptedException e) {
235                 fail("VPN confirmation dialog did not return after " + TIMEOUT_MS + "ms");
236             }
237 
238             // Now we should be prepared.
239             intent = VpnService.prepare(mActivity);
240             if (intent != null) {
241                 fail("VpnService.prepare returned non-null even after the VPN dialog " +
242                      intent.getComponent() + "returned RESULT_OK.");
243             }
244         }
245     }
246 
247     // 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)248     private void startVpn(
249         String[] addresses, String[] routes, String allowedApplications,
250         String disallowedApplications, @Nullable ProxyInfo proxyInfo,
251         @Nullable ArrayList<Network> underlyingNetworks, boolean isAlwaysMetered) throws Exception {
252         prepareVpn();
253 
254         // Register a callback so we will be notified when our VPN comes up.
255         final NetworkRequest request = new NetworkRequest.Builder()
256                 .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
257                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
258                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
259                 .build();
260         mCallback = new NetworkCallback() {
261             public void onAvailable(Network network) {
262                 synchronized (mLock) {
263                     Log.i(TAG, "Got available callback for network=" + network);
264                     mNetwork = network;
265                     mLock.notify();
266                 }
267             }
268         };
269         mCM.registerNetworkCallback(request, mCallback);  // Unregistered in tearDown.
270 
271         // Start the service and wait up for TIMEOUT_MS ms for the VPN to come up.
272         Intent intent = new Intent(mActivity, MyVpnService.class)
273                 .putExtra(mPackageName + ".cmd", "connect")
274                 .putExtra(mPackageName + ".addresses", TextUtils.join(",", addresses))
275                 .putExtra(mPackageName + ".routes", TextUtils.join(",", routes))
276                 .putExtra(mPackageName + ".allowedapplications", allowedApplications)
277                 .putExtra(mPackageName + ".disallowedapplications", disallowedApplications)
278                 .putExtra(mPackageName + ".httpProxy", proxyInfo)
279                 .putParcelableArrayListExtra(
280                     mPackageName + ".underlyingNetworks", underlyingNetworks)
281                 .putExtra(mPackageName + ".isAlwaysMetered", isAlwaysMetered);
282 
283         mActivity.startService(intent);
284         synchronized (mLock) {
285             if (mNetwork == null) {
286                  Log.i(TAG, "bf mLock");
287                  mLock.wait(TIMEOUT_MS);
288                  Log.i(TAG, "af mLock");
289             }
290         }
291 
292         if (mNetwork == null) {
293             fail("VPN did not become available after " + TIMEOUT_MS + "ms");
294         }
295 
296         // Unfortunately, when the available callback fires, the VPN UID ranges are not yet
297         // configured. Give the system some time to do so. http://b/18436087 .
298         try { Thread.sleep(3000); } catch(InterruptedException e) {}
299     }
300 
stopVpn()301     private void stopVpn() {
302         // Register a callback so we will be notified when our VPN comes up.
303         final NetworkRequest request = new NetworkRequest.Builder()
304                 .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
305                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
306                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
307                 .build();
308         mCallback = new NetworkCallback() {
309             public void onLost(Network network) {
310                 synchronized (mLockShutdown) {
311                     Log.i(TAG, "Got lost callback for network=" + network
312                             + ",mNetwork = " + mNetwork);
313                     if( mNetwork == network){
314                         mLockShutdown.notify();
315                     }
316                 }
317             }
318        };
319         mCM.registerNetworkCallback(request, mCallback);  // Unregistered in tearDown.
320         // Simply calling mActivity.stopService() won't stop the service, because the system binds
321         // to the service for the purpose of sending it a revoke command if another VPN comes up,
322         // and stopping a bound service has no effect. Instead, "start" the service again with an
323         // Intent that tells it to disconnect.
324         Intent intent = new Intent(mActivity, MyVpnService.class)
325                 .putExtra(mPackageName + ".cmd", "disconnect");
326         mActivity.startService(intent);
327         synchronized (mLockShutdown) {
328             try {
329                  Log.i(TAG, "bf mLockShutdown");
330                  mLockShutdown.wait(TIMEOUT_MS);
331                  Log.i(TAG, "af mLockShutdown");
332             } catch(InterruptedException e) {}
333         }
334     }
335 
closeQuietly(Closeable c)336     private static void closeQuietly(Closeable c) {
337         if (c != null) {
338             try {
339                 c.close();
340             } catch (IOException e) {
341             }
342         }
343     }
344 
checkPing(String to)345     private static void checkPing(String to) throws IOException, ErrnoException {
346         InetAddress address = InetAddress.getByName(to);
347         FileDescriptor s;
348         final int LENGTH = 64;
349         byte[] packet = new byte[LENGTH];
350         byte[] header;
351 
352         // Construct a ping packet.
353         Random random = new Random();
354         random.nextBytes(packet);
355         if (address instanceof Inet6Address) {
356             s = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
357             header = new byte[] { (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
358         } else {
359             // Note that this doesn't actually work due to http://b/18558481 .
360             s = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
361             header = new byte[] { (byte) 0x08, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
362         }
363         System.arraycopy(header, 0, packet, 0, header.length);
364 
365         // Send the packet.
366         int port = random.nextInt(65534) + 1;
367         Os.connect(s, address, port);
368         Os.write(s, packet, 0, packet.length);
369 
370         // Expect a reply.
371         StructPollfd pollfd = new StructPollfd();
372         pollfd.events = (short) POLLIN;  // "error: possible loss of precision"
373         pollfd.fd = s;
374         int ret = Os.poll(new StructPollfd[] { pollfd }, SOCKET_TIMEOUT_MS);
375         assertEquals("Expected reply after sending ping", 1, ret);
376 
377         byte[] reply = new byte[LENGTH];
378         int read = Os.read(s, reply, 0, LENGTH);
379         assertEquals(LENGTH, read);
380 
381         // Find out what the kernel set the ICMP ID to.
382         InetSocketAddress local = (InetSocketAddress) Os.getsockname(s);
383         port = local.getPort();
384         packet[4] = (byte) ((port >> 8) & 0xff);
385         packet[5] = (byte) (port & 0xff);
386 
387         // Check the contents.
388         if (packet[0] == (byte) 0x80) {
389             packet[0] = (byte) 0x81;
390         } else {
391             packet[0] = 0;
392         }
393         // Zero out the checksum in the reply so it matches the uninitialized checksum in packet.
394         reply[2] = reply[3] = 0;
395         MoreAsserts.assertEquals(packet, reply);
396     }
397 
398     // Writes data to out and checks that it appears identically on in.
writeAndCheckData( OutputStream out, InputStream in, byte[] data)399     private static void writeAndCheckData(
400             OutputStream out, InputStream in, byte[] data) throws IOException {
401         out.write(data, 0, data.length);
402         out.flush();
403 
404         byte[] read = new byte[data.length];
405         int bytesRead = 0, totalRead = 0;
406         do {
407             bytesRead = in.read(read, totalRead, read.length - totalRead);
408             totalRead += bytesRead;
409         } while (bytesRead >= 0 && totalRead < data.length);
410         assertEquals(totalRead, data.length);
411         MoreAsserts.assertEquals(data, read);
412     }
413 
checkTcpReflection(String to, String expectedFrom)414     private void checkTcpReflection(String to, String expectedFrom) throws IOException {
415         // Exercise TCP over the VPN by "connecting to ourselves". We open a server socket and a
416         // client socket, and connect the client socket to a remote host, with the port of the
417         // server socket. The PacketReflector reflects the packets, changing the source addresses
418         // but not the ports, so our client socket is connected to our server socket, though both
419         // sockets think their peers are on the "remote" IP address.
420 
421         // Open a listening socket.
422         ServerSocket listen = new ServerSocket(0, 10, InetAddress.getByName("::"));
423 
424         // Connect the client socket to it.
425         InetAddress toAddr = InetAddress.getByName(to);
426         Socket client = new Socket();
427         try {
428             client.connect(new InetSocketAddress(toAddr, listen.getLocalPort()), SOCKET_TIMEOUT_MS);
429             if (expectedFrom == null) {
430                 closeQuietly(listen);
431                 closeQuietly(client);
432                 fail("Expected connection to fail, but it succeeded.");
433             }
434         } catch (IOException e) {
435             if (expectedFrom != null) {
436                 closeQuietly(listen);
437                 fail("Expected connection to succeed, but it failed.");
438             } else {
439                 // We expected the connection to fail, and it did, so there's nothing more to test.
440                 return;
441             }
442         }
443 
444         // The connection succeeded, and we expected it to succeed. Send some data; if things are
445         // working, the data will be sent to the VPN, reflected by the PacketReflector, and arrive
446         // at our server socket. For good measure, send some data in the other direction.
447         Socket server = null;
448         try {
449             // Accept the connection on the server side.
450             listen.setSoTimeout(SOCKET_TIMEOUT_MS);
451             server = listen.accept();
452             checkConnectionOwnerUidTcp(client);
453             checkConnectionOwnerUidTcp(server);
454             // Check that the source and peer addresses are as expected.
455             assertEquals(expectedFrom, client.getLocalAddress().getHostAddress());
456             assertEquals(expectedFrom, server.getLocalAddress().getHostAddress());
457             assertEquals(
458                     new InetSocketAddress(toAddr, client.getLocalPort()),
459                     server.getRemoteSocketAddress());
460             assertEquals(
461                     new InetSocketAddress(toAddr, server.getLocalPort()),
462                     client.getRemoteSocketAddress());
463 
464             // Now write some data.
465             final int LENGTH = 32768;
466             byte[] data = new byte[LENGTH];
467             new Random().nextBytes(data);
468 
469             // Make sure our writes don't block or time out, because we're single-threaded and can't
470             // read and write at the same time.
471             server.setReceiveBufferSize(LENGTH * 2);
472             client.setSendBufferSize(LENGTH * 2);
473             client.setSoTimeout(SOCKET_TIMEOUT_MS);
474             server.setSoTimeout(SOCKET_TIMEOUT_MS);
475 
476             // Send some data from client to server, then from server to client.
477             writeAndCheckData(client.getOutputStream(), server.getInputStream(), data);
478             writeAndCheckData(server.getOutputStream(), client.getInputStream(), data);
479         } finally {
480             closeQuietly(listen);
481             closeQuietly(client);
482             closeQuietly(server);
483         }
484     }
485 
checkConnectionOwnerUidUdp(DatagramSocket s, boolean expectSuccess)486     private void checkConnectionOwnerUidUdp(DatagramSocket s, boolean expectSuccess) {
487         final int expectedUid = expectSuccess ? Process.myUid() : INVALID_UID;
488         InetSocketAddress loc = new InetSocketAddress(s.getLocalAddress(), s.getLocalPort());
489         InetSocketAddress rem = new InetSocketAddress(s.getInetAddress(), s.getPort());
490         int uid = mCM.getConnectionOwnerUid(OsConstants.IPPROTO_UDP, loc, rem);
491         assertEquals(expectedUid, uid);
492     }
493 
checkConnectionOwnerUidTcp(Socket s)494     private void checkConnectionOwnerUidTcp(Socket s) {
495         final int expectedUid = Process.myUid();
496         InetSocketAddress loc = new InetSocketAddress(s.getLocalAddress(), s.getLocalPort());
497         InetSocketAddress rem = new InetSocketAddress(s.getInetAddress(), s.getPort());
498         int uid = mCM.getConnectionOwnerUid(OsConstants.IPPROTO_TCP, loc, rem);
499         assertEquals(expectedUid, uid);
500     }
501 
checkUdpEcho(String to, String expectedFrom)502     private void checkUdpEcho(String to, String expectedFrom) throws IOException {
503         DatagramSocket s;
504         InetAddress address = InetAddress.getByName(to);
505         if (address instanceof Inet6Address) {  // http://b/18094870
506             s = new DatagramSocket(0, InetAddress.getByName("::"));
507         } else {
508             s = new DatagramSocket();
509         }
510         s.setSoTimeout(SOCKET_TIMEOUT_MS);
511 
512         Random random = new Random();
513         byte[] data = new byte[random.nextInt(1650)];
514         random.nextBytes(data);
515         DatagramPacket p = new DatagramPacket(data, data.length);
516         s.connect(address, 7);
517 
518         if (expectedFrom != null) {
519             assertEquals("Unexpected source address: ",
520                          expectedFrom, s.getLocalAddress().getHostAddress());
521         }
522 
523         try {
524             if (expectedFrom != null) {
525                 s.send(p);
526                 checkConnectionOwnerUidUdp(s, true);
527                 s.receive(p);
528                 MoreAsserts.assertEquals(data, p.getData());
529             } else {
530                 try {
531                     s.send(p);
532                     s.receive(p);
533                     fail("Received unexpected reply");
534                 } catch (IOException expected) {
535                     checkConnectionOwnerUidUdp(s, false);
536                 }
537             }
538         } finally {
539             s.close();
540         }
541     }
542 
checkTrafficOnVpn()543     private void checkTrafficOnVpn() throws Exception {
544         checkUdpEcho("192.0.2.251", "192.0.2.2");
545         checkUdpEcho("2001:db8:dead:beef::f00", "2001:db8:1:2::ffe");
546         checkPing("2001:db8:dead:beef::f00");
547         checkTcpReflection("192.0.2.252", "192.0.2.2");
548         checkTcpReflection("2001:db8:dead:beef::f00", "2001:db8:1:2::ffe");
549     }
550 
checkNoTrafficOnVpn()551     private void checkNoTrafficOnVpn() throws Exception {
552         checkUdpEcho("192.0.2.251", null);
553         checkUdpEcho("2001:db8:dead:beef::f00", null);
554         checkTcpReflection("192.0.2.252", null);
555         checkTcpReflection("2001:db8:dead:beef::f00", null);
556     }
557 
openSocketFd(String host, int port, int timeoutMs)558     private FileDescriptor openSocketFd(String host, int port, int timeoutMs) throws Exception {
559         Socket s = new Socket(host, port);
560         s.setSoTimeout(timeoutMs);
561         // Dup the filedescriptor so ParcelFileDescriptor's finalizer doesn't garbage collect it
562         // and cause our fd to become invalid. http://b/35927643 .
563         FileDescriptor fd = Os.dup(ParcelFileDescriptor.fromSocket(s).getFileDescriptor());
564         s.close();
565         return fd;
566     }
567 
openSocketFdInOtherApp( String host, int port, int timeoutMs)568     private FileDescriptor openSocketFdInOtherApp(
569             String host, int port, int timeoutMs) throws Exception {
570         Log.d(TAG, String.format("Creating test socket in UID=%d, my UID=%d",
571                 mRemoteSocketFactoryClient.getUid(), Os.getuid()));
572         FileDescriptor fd = mRemoteSocketFactoryClient.openSocketFd(host, port, TIMEOUT_MS);
573         return fd;
574     }
575 
sendRequest(FileDescriptor fd, String host)576     private void sendRequest(FileDescriptor fd, String host) throws Exception {
577         String request = "GET /generate_204 HTTP/1.1\r\n" +
578                 "Host: " + host + "\r\n" +
579                 "Connection: keep-alive\r\n\r\n";
580         byte[] requestBytes = request.getBytes(StandardCharsets.UTF_8);
581         int ret = Os.write(fd, requestBytes, 0, requestBytes.length);
582         Log.d(TAG, "Wrote " + ret + "bytes");
583 
584         String expected = "HTTP/1.1 204 No Content\r\n";
585         byte[] response = new byte[expected.length()];
586         Os.read(fd, response, 0, response.length);
587 
588         String actual = new String(response, StandardCharsets.UTF_8);
589         assertEquals(expected, actual);
590         Log.d(TAG, "Got response: " + actual);
591     }
592 
assertSocketStillOpen(FileDescriptor fd, String host)593     private void assertSocketStillOpen(FileDescriptor fd, String host) throws Exception {
594         try {
595             assertTrue(fd.valid());
596             sendRequest(fd, host);
597             assertTrue(fd.valid());
598         } finally {
599             Os.close(fd);
600         }
601     }
602 
assertSocketClosed(FileDescriptor fd, String host)603     private void assertSocketClosed(FileDescriptor fd, String host) throws Exception {
604         try {
605             assertTrue(fd.valid());
606             sendRequest(fd, host);
607             fail("Socket opened before VPN connects should be closed when VPN connects");
608         } catch (ErrnoException expected) {
609             assertEquals(ECONNABORTED, expected.errno);
610             assertTrue(fd.valid());
611         } finally {
612             Os.close(fd);
613         }
614     }
615 
getContentResolver()616     private ContentResolver getContentResolver() {
617         return getInstrumentation().getContext().getContentResolver();
618     }
619 
isPrivateDnsInStrictMode()620     private boolean isPrivateDnsInStrictMode() {
621         return PRIVATE_DNS_MODE_PROVIDER_HOSTNAME.equals(
622                 Settings.Global.getString(getContentResolver(), PRIVATE_DNS_MODE_SETTING));
623     }
624 
storePrivateDnsSetting()625     private void storePrivateDnsSetting() {
626         mOldPrivateDnsMode = Settings.Global.getString(getContentResolver(),
627                 PRIVATE_DNS_MODE_SETTING);
628         mOldPrivateDnsSpecifier = Settings.Global.getString(getContentResolver(),
629                 PRIVATE_DNS_SPECIFIER_SETTING);
630     }
631 
restorePrivateDnsSetting()632     private void restorePrivateDnsSetting() {
633         Settings.Global.putString(getContentResolver(), PRIVATE_DNS_MODE_SETTING,
634                 mOldPrivateDnsMode);
635         Settings.Global.putString(getContentResolver(), PRIVATE_DNS_SPECIFIER_SETTING,
636                 mOldPrivateDnsSpecifier);
637     }
638 
639     // TODO: replace with CtsNetUtils.awaitPrivateDnsSetting in Q or above.
expectPrivateDnsHostname(final String hostname)640     private void expectPrivateDnsHostname(final String hostname) throws Exception {
641         final NetworkRequest request = new NetworkRequest.Builder()
642                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
643                 .build();
644         final CountDownLatch latch = new CountDownLatch(1);
645         final NetworkCallback callback = new NetworkCallback() {
646             @Override
647             public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
648                 if (network.equals(mNetwork) &&
649                         Objects.equals(lp.getPrivateDnsServerName(), hostname)) {
650                     latch.countDown();
651                 }
652             }
653         };
654 
655         mCM.registerNetworkCallback(request, callback);
656 
657         try {
658             assertTrue("Private DNS hostname was not " + hostname + " after " + TIMEOUT_MS + "ms",
659                     latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
660         } finally {
661             mCM.unregisterNetworkCallback(callback);
662         }
663     }
664 
setAndVerifyPrivateDns(boolean strictMode)665     private void setAndVerifyPrivateDns(boolean strictMode) throws Exception {
666         final ContentResolver cr = getInstrumentation().getContext().getContentResolver();
667         String privateDnsHostname;
668 
669         if (strictMode) {
670             privateDnsHostname = "vpncts-nx.metric.gstatic.com";
671             Settings.Global.putString(cr, PRIVATE_DNS_SPECIFIER_SETTING, privateDnsHostname);
672             Settings.Global.putString(cr, PRIVATE_DNS_MODE_SETTING,
673                     PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
674         } else {
675             Settings.Global.putString(cr, PRIVATE_DNS_MODE_SETTING, PRIVATE_DNS_MODE_OPPORTUNISTIC);
676             privateDnsHostname = null;
677         }
678 
679         expectPrivateDnsHostname(privateDnsHostname);
680 
681         String randomName = "vpncts-" + new Random().nextInt(1000000000) + "-ds.metric.gstatic.com";
682         if (strictMode) {
683             // Strict mode private DNS is enabled. DNS lookups should fail, because the private DNS
684             // server name is invalid.
685             try {
686                 InetAddress.getByName(randomName);
687                 fail("VPN DNS lookup should fail with private DNS enabled");
688             } catch (UnknownHostException expected) {
689             }
690         } else {
691             // Strict mode private DNS is disabled. DNS lookup should succeed, because the VPN
692             // provides no DNS servers, and thus DNS falls through to the default network.
693             assertNotNull("VPN DNS lookup should succeed with private DNS disabled",
694                     InetAddress.getByName(randomName));
695         }
696     }
697 
698     // Tests that strict mode private DNS is used on VPNs.
checkStrictModePrivateDns()699     private void checkStrictModePrivateDns() throws Exception {
700         final boolean initialMode = isPrivateDnsInStrictMode();
701         setAndVerifyPrivateDns(!initialMode);
702         setAndVerifyPrivateDns(initialMode);
703     }
704 
testDefault()705     public void testDefault() throws Exception {
706         if (!supportedHardware()) return;
707         // If adb TCP port opened, this test may running by adb over network.
708         // All of socket would be destroyed in this test. So this test don't
709         // support adb over network, see b/119382723.
710         if (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1
711                 || SystemProperties.getInt("service.adb.tcp.port", -1) > -1) {
712             Log.i(TAG, "adb is running over the network, so skip this test");
713             return;
714         }
715 
716         final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(
717                 getInstrumentation().getTargetContext(), MyVpnService.ACTION_ESTABLISHED);
718         receiver.register();
719 
720         // Test the behaviour of a variety of types of network callbacks.
721         final Network defaultNetwork = mCM.getActiveNetwork();
722         final TestableNetworkCallback systemDefaultCallback = new TestableNetworkCallback();
723         final TestableNetworkCallback otherUidCallback = new TestableNetworkCallback();
724         final TestableNetworkCallback myUidCallback = new TestableNetworkCallback();
725         if (SdkLevel.isAtLeastS()) {
726             final int otherUid =
727                     UserHandle.of(5 /* userId */).getUid(Process.FIRST_APPLICATION_UID);
728             final Handler h = new Handler(Looper.getMainLooper());
729             runWithShellPermissionIdentity(() -> {
730                 mCM.registerSystemDefaultNetworkCallback(systemDefaultCallback, h);
731                 mCM.registerDefaultNetworkCallbackForUid(otherUid, otherUidCallback, h);
732                 mCM.registerDefaultNetworkCallbackForUid(Process.myUid(), myUidCallback, h);
733             }, NETWORK_SETTINGS);
734             for (TestableNetworkCallback callback :
735                     List.of(systemDefaultCallback, otherUidCallback, myUidCallback)) {
736                 callback.expectAvailableCallbacks(defaultNetwork, false /* suspended */,
737                         true /* validated */, false /* blocked */, TIMEOUT_MS);
738             }
739         }
740 
741         FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
742 
743         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
744                  new String[] {"0.0.0.0/0", "::/0"},
745                  "", "", null, null /* underlyingNetworks */, false /* isAlwaysMetered */);
746 
747         final Intent intent = receiver.awaitForBroadcast(TimeUnit.MINUTES.toMillis(1));
748         assertNotNull("Failed to receive broadcast from VPN service", intent);
749         assertFalse("Wrong VpnService#isAlwaysOn",
750                 intent.getBooleanExtra(MyVpnService.EXTRA_ALWAYS_ON, true));
751         assertFalse("Wrong VpnService#isLockdownEnabled",
752                 intent.getBooleanExtra(MyVpnService.EXTRA_LOCKDOWN_ENABLED, true));
753 
754         assertSocketClosed(fd, TEST_HOST);
755 
756         checkTrafficOnVpn();
757 
758         final Network vpnNetwork = mCM.getActiveNetwork();
759         myUidCallback.expectAvailableThenValidatedCallbacks(vpnNetwork, TIMEOUT_MS);
760         assertEquals(vpnNetwork, mCM.getActiveNetwork());
761         assertNotEqual(defaultNetwork, vpnNetwork);
762         maybeExpectVpnTransportInfo(vpnNetwork);
763         assertEquals(TYPE_VPN, mCM.getNetworkInfo(vpnNetwork).getType());
764 
765         if (SdkLevel.isAtLeastS()) {
766             // Check that system default network callback has not seen any network changes, even
767             // though the app's default network changed. Also check that otherUidCallback saw no
768             // network changes, because otherUid is in a different user and not subject to the VPN.
769             // This needs to be done before testing  private DNS because checkStrictModePrivateDns
770             // will set the private DNS server to a nonexistent name, which will cause validation to
771             // fail and could cause the default network to switch (e.g., from wifi to cellular).
772             systemDefaultCallback.assertNoCallback();
773             otherUidCallback.assertNoCallback();
774             mCM.unregisterNetworkCallback(systemDefaultCallback);
775             mCM.unregisterNetworkCallback(otherUidCallback);
776             mCM.unregisterNetworkCallback(myUidCallback);
777         }
778 
779         checkStrictModePrivateDns();
780 
781         receiver.unregisterQuietly();
782     }
783 
testAppAllowed()784     public void testAppAllowed() throws Exception {
785         if (!supportedHardware()) return;
786 
787         FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
788 
789         // Shell app must not be put in here or it would kill the ADB-over-network use case
790         String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
791         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
792                  new String[] {"192.0.2.0/24", "2001:db8::/32"},
793                  allowedApps, "", null, null /* underlyingNetworks */, false /* isAlwaysMetered */);
794 
795         assertSocketClosed(fd, TEST_HOST);
796 
797         checkTrafficOnVpn();
798 
799         maybeExpectVpnTransportInfo(mCM.getActiveNetwork());
800 
801         checkStrictModePrivateDns();
802     }
803 
testAppDisallowed()804     public void testAppDisallowed() throws Exception {
805         if (!supportedHardware()) return;
806 
807         FileDescriptor localFd = openSocketFd(TEST_HOST, 80, TIMEOUT_MS);
808         FileDescriptor remoteFd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
809 
810         String disallowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
811         // If adb TCP port opened, this test may running by adb over TCP.
812         // Add com.android.shell appllication into blacklist to exclude adb socket for VPN test,
813         // see b/119382723.
814         // Note: The test don't support running adb over network for root device
815         disallowedApps = disallowedApps + ",com.android.shell";
816         Log.i(TAG, "Append shell app to disallowedApps: " + disallowedApps);
817         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
818                  new String[] {"192.0.2.0/24", "2001:db8::/32"},
819                  "", disallowedApps, null, null /* underlyingNetworks */,
820                  false /* isAlwaysMetered */);
821 
822         assertSocketStillOpen(localFd, TEST_HOST);
823         assertSocketStillOpen(remoteFd, TEST_HOST);
824 
825         checkNoTrafficOnVpn();
826 
827         final Network network = mCM.getActiveNetwork();
828         final NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
829         assertFalse(nc.hasTransport(TRANSPORT_VPN));
830     }
831 
testGetConnectionOwnerUidSecurity()832     public void testGetConnectionOwnerUidSecurity() throws Exception {
833         if (!supportedHardware()) return;
834 
835         DatagramSocket s;
836         InetAddress address = InetAddress.getByName("localhost");
837         s = new DatagramSocket();
838         s.setSoTimeout(SOCKET_TIMEOUT_MS);
839         s.connect(address, 7);
840         InetSocketAddress loc = new InetSocketAddress(s.getLocalAddress(), s.getLocalPort());
841         InetSocketAddress rem = new InetSocketAddress(s.getInetAddress(), s.getPort());
842         try {
843             int uid = mCM.getConnectionOwnerUid(OsConstants.IPPROTO_TCP, loc, rem);
844             assertEquals("Only an active VPN app should see connection information",
845                     INVALID_UID, uid);
846         } catch (SecurityException acceptable) {
847             // R and below throw SecurityException if a non-active VPN calls this method.
848             // As long as we can't actually get socket information, either behaviour is fine.
849             return;
850         }
851     }
852 
testSetProxy()853     public void testSetProxy() throws  Exception {
854         if (!supportedHardware()) return;
855         ProxyInfo initialProxy = mCM.getDefaultProxy();
856         // Receiver for the proxy change broadcast.
857         BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
858         proxyBroadcastReceiver.register();
859 
860         String allowedApps = mPackageName;
861         ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("10.0.0.1", 8888);
862         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
863                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "",
864                 testProxyInfo, null /* underlyingNetworks */, false /* isAlwaysMetered */);
865 
866         // Check that the proxy change broadcast is received
867         try {
868             assertNotNull("No proxy change was broadcast when VPN is connected.",
869                     proxyBroadcastReceiver.awaitForBroadcast());
870         } finally {
871             proxyBroadcastReceiver.unregisterQuietly();
872         }
873 
874         // Proxy is set correctly in network and in link properties.
875         assertNetworkHasExpectedProxy(testProxyInfo, mNetwork);
876         assertDefaultProxy(testProxyInfo);
877 
878         proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
879         proxyBroadcastReceiver.register();
880         stopVpn();
881         try {
882             assertNotNull("No proxy change was broadcast when VPN was disconnected.",
883                     proxyBroadcastReceiver.awaitForBroadcast());
884         } finally {
885             proxyBroadcastReceiver.unregisterQuietly();
886         }
887 
888         // After disconnecting from VPN, the proxy settings are the ones of the initial network.
889         assertDefaultProxy(initialProxy);
890     }
891 
testSetProxyDisallowedApps()892     public void testSetProxyDisallowedApps() throws Exception {
893         if (!supportedHardware()) return;
894         ProxyInfo initialProxy = mCM.getDefaultProxy();
895 
896         // If adb TCP port opened, this test may running by adb over TCP.
897         // Add com.android.shell appllication into blacklist to exclude adb socket for VPN test,
898         // see b/119382723.
899         // Note: The test don't support running adb over network for root device
900         String disallowedApps = mPackageName + ",com.android.shell";
901         ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("10.0.0.1", 8888);
902         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
903                 new String[] {"0.0.0.0/0", "::/0"}, "", disallowedApps,
904                 testProxyInfo, null /* underlyingNetworks */, false /* isAlwaysMetered */);
905 
906         // The disallowed app does has the proxy configs of the default network.
907         assertNetworkHasExpectedProxy(initialProxy, mCM.getActiveNetwork());
908         assertDefaultProxy(initialProxy);
909     }
910 
testNoProxy()911     public void testNoProxy() throws Exception {
912         if (!supportedHardware()) return;
913         ProxyInfo initialProxy = mCM.getDefaultProxy();
914         BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
915         proxyBroadcastReceiver.register();
916         String allowedApps = mPackageName;
917         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
918                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
919                 null /* underlyingNetworks */, false /* isAlwaysMetered */);
920 
921         try {
922             assertNotNull("No proxy change was broadcast.",
923                     proxyBroadcastReceiver.awaitForBroadcast());
924         } finally {
925             proxyBroadcastReceiver.unregisterQuietly();
926         }
927 
928         // The VPN network has no proxy set.
929         assertNetworkHasExpectedProxy(null, mNetwork);
930 
931         proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
932         proxyBroadcastReceiver.register();
933         stopVpn();
934         try {
935             assertNotNull("No proxy change was broadcast.",
936                     proxyBroadcastReceiver.awaitForBroadcast());
937         } finally {
938             proxyBroadcastReceiver.unregisterQuietly();
939         }
940         // After disconnecting from VPN, the proxy settings are the ones of the initial network.
941         assertDefaultProxy(initialProxy);
942         assertNetworkHasExpectedProxy(initialProxy, mCM.getActiveNetwork());
943     }
944 
testBindToNetworkWithProxy()945     public void testBindToNetworkWithProxy() throws Exception {
946         if (!supportedHardware()) return;
947         String allowedApps = mPackageName;
948         Network initialNetwork = mCM.getActiveNetwork();
949         ProxyInfo initialProxy = mCM.getDefaultProxy();
950         ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("10.0.0.1", 8888);
951         // Receiver for the proxy change broadcast.
952         BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
953         proxyBroadcastReceiver.register();
954         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
955                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "",
956                 testProxyInfo, null /* underlyingNetworks */, false /* isAlwaysMetered */);
957 
958         assertDefaultProxy(testProxyInfo);
959         mCM.bindProcessToNetwork(initialNetwork);
960         try {
961             assertNotNull("No proxy change was broadcast.",
962                 proxyBroadcastReceiver.awaitForBroadcast());
963         } finally {
964             proxyBroadcastReceiver.unregisterQuietly();
965         }
966         assertDefaultProxy(initialProxy);
967     }
968 
testVpnMeterednessWithNoUnderlyingNetwork()969     public void testVpnMeterednessWithNoUnderlyingNetwork() throws Exception {
970         if (!supportedHardware()) {
971             return;
972         }
973         // VPN is not routing any traffic i.e. its underlying networks is an empty array.
974         ArrayList<Network> underlyingNetworks = new ArrayList<>();
975         String allowedApps = mPackageName;
976 
977         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
978                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
979                 underlyingNetworks, false /* isAlwaysMetered */);
980 
981         // VPN should now be the active network.
982         assertEquals(mNetwork, mCM.getActiveNetwork());
983         assertVpnTransportContains(NetworkCapabilities.TRANSPORT_VPN);
984         // VPN with no underlying networks should be metered by default.
985         assertTrue(isNetworkMetered(mNetwork));
986         assertTrue(mCM.isActiveNetworkMetered());
987 
988         maybeExpectVpnTransportInfo(mCM.getActiveNetwork());
989     }
990 
testVpnMeterednessWithNullUnderlyingNetwork()991     public void testVpnMeterednessWithNullUnderlyingNetwork() throws Exception {
992         if (!supportedHardware()) {
993             return;
994         }
995         Network underlyingNetwork = mCM.getActiveNetwork();
996         if (underlyingNetwork == null) {
997             Log.i(TAG, "testVpnMeterednessWithNullUnderlyingNetwork cannot execute"
998                     + " unless there is an active network");
999             return;
1000         }
1001         // VPN tracks platform default.
1002         ArrayList<Network> underlyingNetworks = null;
1003         String allowedApps = mPackageName;
1004 
1005         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
1006                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
1007                 underlyingNetworks, false /*isAlwaysMetered */);
1008 
1009         // Ensure VPN transports contains underlying network's transports.
1010         assertVpnTransportContains(underlyingNetwork);
1011         // Its meteredness should be same as that of underlying network.
1012         assertEquals(isNetworkMetered(underlyingNetwork), isNetworkMetered(mNetwork));
1013         // Meteredness based on VPN capabilities and CM#isActiveNetworkMetered should be in sync.
1014         assertEquals(isNetworkMetered(mNetwork), mCM.isActiveNetworkMetered());
1015 
1016         maybeExpectVpnTransportInfo(mCM.getActiveNetwork());
1017     }
1018 
testVpnMeterednessWithNonNullUnderlyingNetwork()1019     public void testVpnMeterednessWithNonNullUnderlyingNetwork() throws Exception {
1020         if (!supportedHardware()) {
1021             return;
1022         }
1023         Network underlyingNetwork = mCM.getActiveNetwork();
1024         if (underlyingNetwork == null) {
1025             Log.i(TAG, "testVpnMeterednessWithNonNullUnderlyingNetwork cannot execute"
1026                     + " unless there is an active network");
1027             return;
1028         }
1029         // VPN explicitly declares WiFi to be its underlying network.
1030         ArrayList<Network> underlyingNetworks = new ArrayList<>(1);
1031         underlyingNetworks.add(underlyingNetwork);
1032         String allowedApps = mPackageName;
1033 
1034         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
1035                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
1036                 underlyingNetworks, false /* isAlwaysMetered */);
1037 
1038         // Ensure VPN transports contains underlying network's transports.
1039         assertVpnTransportContains(underlyingNetwork);
1040         // Its meteredness should be same as that of underlying network.
1041         assertEquals(isNetworkMetered(underlyingNetwork), isNetworkMetered(mNetwork));
1042         // Meteredness based on VPN capabilities and CM#isActiveNetworkMetered should be in sync.
1043         assertEquals(isNetworkMetered(mNetwork), mCM.isActiveNetworkMetered());
1044 
1045         maybeExpectVpnTransportInfo(mCM.getActiveNetwork());
1046     }
1047 
testAlwaysMeteredVpnWithNullUnderlyingNetwork()1048     public void testAlwaysMeteredVpnWithNullUnderlyingNetwork() throws Exception {
1049         if (!supportedHardware()) {
1050             return;
1051         }
1052         Network underlyingNetwork = mCM.getActiveNetwork();
1053         if (underlyingNetwork == null) {
1054             Log.i(TAG, "testAlwaysMeteredVpnWithNullUnderlyingNetwork cannot execute"
1055                     + " unless there is an active network");
1056             return;
1057         }
1058         // VPN tracks platform default.
1059         ArrayList<Network> underlyingNetworks = null;
1060         String allowedApps = mPackageName;
1061         boolean isAlwaysMetered = true;
1062 
1063         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
1064                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
1065                 underlyingNetworks, isAlwaysMetered);
1066 
1067         // VPN's meteredness does not depend on underlying network since it is always metered.
1068         assertTrue(isNetworkMetered(mNetwork));
1069         assertTrue(mCM.isActiveNetworkMetered());
1070 
1071         maybeExpectVpnTransportInfo(mCM.getActiveNetwork());
1072     }
1073 
testAlwaysMeteredVpnWithNonNullUnderlyingNetwork()1074     public void testAlwaysMeteredVpnWithNonNullUnderlyingNetwork() throws Exception {
1075         if (!supportedHardware()) {
1076             return;
1077         }
1078         Network underlyingNetwork = mCM.getActiveNetwork();
1079         if (underlyingNetwork == null) {
1080             Log.i(TAG, "testAlwaysMeteredVpnWithNonNullUnderlyingNetwork cannot execute"
1081                     + " unless there is an active network");
1082             return;
1083         }
1084         // VPN explicitly declares its underlying network.
1085         ArrayList<Network> underlyingNetworks = new ArrayList<>(1);
1086         underlyingNetworks.add(underlyingNetwork);
1087         String allowedApps = mPackageName;
1088         boolean isAlwaysMetered = true;
1089 
1090         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
1091                 new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null,
1092                 underlyingNetworks, isAlwaysMetered);
1093 
1094         // VPN's meteredness does not depend on underlying network since it is always metered.
1095         assertTrue(isNetworkMetered(mNetwork));
1096         assertTrue(mCM.isActiveNetworkMetered());
1097 
1098         maybeExpectVpnTransportInfo(mCM.getActiveNetwork());
1099     }
1100 
testB141603906()1101     public void testB141603906() throws Exception {
1102         if (!supportedHardware()) {
1103             return;
1104         }
1105         final InetSocketAddress src = new InetSocketAddress(0);
1106         final InetSocketAddress dst = new InetSocketAddress(0);
1107         final int NUM_THREADS = 8;
1108         final int NUM_SOCKETS = 5000;
1109         final Thread[] threads = new Thread[NUM_THREADS];
1110         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
1111                  new String[] {"0.0.0.0/0", "::/0"},
1112                  "" /* allowedApplications */, "com.android.shell" /* disallowedApplications */,
1113                 null /* proxyInfo */, null /* underlyingNetworks */, false /* isAlwaysMetered */);
1114 
1115         for (int i = 0; i < NUM_THREADS; i++) {
1116             threads[i] = new Thread(() -> {
1117                 for (int j = 0; j < NUM_SOCKETS; j++) {
1118                     mCM.getConnectionOwnerUid(IPPROTO_TCP, src, dst);
1119                 }
1120             });
1121         }
1122         for (Thread thread : threads) {
1123             thread.start();
1124         }
1125         for (Thread thread : threads) {
1126             thread.join();
1127         }
1128         stopVpn();
1129     }
1130 
isNetworkMetered(Network network)1131     private boolean isNetworkMetered(Network network) {
1132         NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
1133         return !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
1134     }
1135 
assertVpnTransportContains(Network underlyingNetwork)1136     private void assertVpnTransportContains(Network underlyingNetwork) {
1137         int[] transports = mCM.getNetworkCapabilities(underlyingNetwork).getTransportTypes();
1138         assertVpnTransportContains(transports);
1139     }
1140 
assertVpnTransportContains(int... transports)1141     private void assertVpnTransportContains(int... transports) {
1142         NetworkCapabilities vpnCaps = mCM.getNetworkCapabilities(mNetwork);
1143         for (int transport : transports) {
1144             assertTrue(vpnCaps.hasTransport(transport));
1145         }
1146     }
1147 
maybeExpectVpnTransportInfo(Network network)1148     private void maybeExpectVpnTransportInfo(Network network) {
1149         if (!SdkLevel.isAtLeastS()) return;
1150         final NetworkCapabilities vpnNc = mCM.getNetworkCapabilities(network);
1151         assertTrue(vpnNc.hasTransport(TRANSPORT_VPN));
1152         final TransportInfo ti = vpnNc.getTransportInfo();
1153         assertTrue(ti instanceof VpnTransportInfo);
1154         assertEquals(VpnManager.TYPE_VPN_SERVICE, ((VpnTransportInfo) ti).getType());
1155     }
1156 
assertDefaultProxy(ProxyInfo expected)1157     private void assertDefaultProxy(ProxyInfo expected) {
1158         assertEquals("Incorrect proxy config.", expected, mCM.getDefaultProxy());
1159         String expectedHost = expected == null ? null : expected.getHost();
1160         String expectedPort = expected == null ? null : String.valueOf(expected.getPort());
1161         assertEquals("Incorrect proxy host system property.", expectedHost,
1162             System.getProperty("http.proxyHost"));
1163         assertEquals("Incorrect proxy port system property.", expectedPort,
1164             System.getProperty("http.proxyPort"));
1165     }
1166 
assertNetworkHasExpectedProxy(ProxyInfo expected, Network network)1167     private void assertNetworkHasExpectedProxy(ProxyInfo expected, Network network) {
1168         LinkProperties lp = mCM.getLinkProperties(network);
1169         assertNotNull("The network link properties object is null.", lp);
1170         assertEquals("Incorrect proxy config.", expected, lp.getHttpProxy());
1171 
1172         assertEquals(expected, mCM.getProxyForNetwork(network));
1173     }
1174 
1175     class ProxyChangeBroadcastReceiver extends BlockingBroadcastReceiver {
1176         private boolean received;
1177 
ProxyChangeBroadcastReceiver()1178         public ProxyChangeBroadcastReceiver() {
1179             super(VpnTest.this.getInstrumentation().getContext(), Proxy.PROXY_CHANGE_ACTION);
1180             received = false;
1181         }
1182 
1183         @Override
onReceive(Context context, Intent intent)1184         public void onReceive(Context context, Intent intent) {
1185             if (!received) {
1186                 // Do not call onReceive() more than once.
1187                 super.onReceive(context, intent);
1188             }
1189             received = true;
1190         }
1191     }
1192 
1193     /**
1194      * Verifies that DownloadManager has CONNECTIVITY_USE_RESTRICTED_NETWORKS permission that can
1195      * bind socket to VPN when it is in VPN disallowed list but requested downloading app is in VPN
1196      * allowed list.
1197      * See b/165774987.
1198      */
testDownloadWithDownloadManagerDisallowed()1199     public void testDownloadWithDownloadManagerDisallowed() throws Exception {
1200         if (!supportedHardware()) return;
1201 
1202         // Start a VPN with DownloadManager package in disallowed list.
1203         startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
1204                 new String[] {"192.0.2.0/24", "2001:db8::/32"},
1205                 "" /* allowedApps */, "com.android.providers.downloads", null /* proxyInfo */,
1206                 null /* underlyingNetworks */, false /* isAlwaysMetered */);
1207 
1208         final Context context = VpnTest.this.getInstrumentation().getContext();
1209         final DownloadManager dm = context.getSystemService(DownloadManager.class);
1210         final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
1211         try {
1212             context.registerReceiver(receiver,
1213                     new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
1214 
1215             // Enqueue a request and check only one download.
1216             final long id = dm.enqueue(new Request(
1217                     Uri.parse("https://google-ipv6test.appspot.com/ip.js?fmt=text")));
1218             assertEquals(1, getTotalNumberDownloads(dm, new Query()));
1219             assertEquals(1, getTotalNumberDownloads(dm, new Query().setFilterById(id)));
1220 
1221             // Wait for download complete and check status.
1222             assertEquals(id, receiver.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
1223             assertEquals(1, getTotalNumberDownloads(dm,
1224                     new Query().setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL)));
1225 
1226             // Remove download.
1227             assertEquals(1, dm.remove(id));
1228             assertEquals(0, getTotalNumberDownloads(dm, new Query()));
1229         } finally {
1230             context.unregisterReceiver(receiver);
1231         }
1232     }
1233 
getTotalNumberDownloads(final DownloadManager dm, final Query query)1234     private static int getTotalNumberDownloads(final DownloadManager dm, final Query query) {
1235         try (Cursor cursor = dm.query(query)) { return cursor.getCount(); }
1236     }
1237 
1238     private static class DownloadCompleteReceiver extends BroadcastReceiver {
1239         private final CompletableFuture<Long> future = new CompletableFuture<>();
1240 
1241         @Override
onReceive(Context context, Intent intent)1242         public void onReceive(Context context, Intent intent) {
1243             future.complete(intent.getLongExtra(
1244                     DownloadManager.EXTRA_DOWNLOAD_ID, -1 /* defaultValue */));
1245         }
1246 
get(long timeout, TimeUnit unit)1247         public long get(long timeout, TimeUnit unit) throws Exception {
1248             return future.get(timeout, unit);
1249         }
1250     }
1251 }
1252