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