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