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