From 5d3ac8f9e1de786c05f3091549bd4eec3fda42a6 Mon Sep 17 00:00:00 2001 From: Varun Anand Date: Tue, 12 Mar 2019 16:02:32 -0700 Subject: [PATCH] CTS tests related to VPN meteredness. (cherry picked from commit d1008aa730886a52e39ddb9c384fd563806a1501) Tests cover scenarios related to whether VPN has explicitly declared its underlying networks plus whether it is an always metered VPN. For each of these scenarios, we ensure VPN meteredness based on its capabilities and ConnectivityManager#isActiveNetworkMetered matches. Bug: 123727651 Test: atest HostsideVpnTests Change-Id: I2dea70c1c432d05b1a22c945f1e3e17166e4132d Merged-In: I3030e5468a55bbc32be2a753f098dcf7f0256af8 --- .../cts/net/hostside/MyVpnService.java | 14 ++ .../com/android/cts/net/hostside/VpnTest.java | 175 ++++++++++++++++-- .../com/android/cts/net/HostsideVpnTests.java | 27 +++ 3 files changed, 205 insertions(+), 11 deletions(-) diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java index 7d91574c82..7d3d4fce74 100644 --- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java +++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java @@ -17,6 +17,7 @@ package com.android.cts.net.hostside; import android.content.Intent; +import android.net.Network; import android.net.ProxyInfo; import android.net.VpnService; import android.os.ParcelFileDescriptor; @@ -27,6 +28,7 @@ import android.util.Log; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.ArrayList; public class MyVpnService extends VpnService { @@ -118,6 +120,18 @@ public class MyVpnService extends VpnService { } } + ArrayList underlyingNetworks = + intent.getParcelableArrayListExtra(packageName + ".underlyingNetworks"); + if (underlyingNetworks == null) { + // VPN tracks default network + builder.setUnderlyingNetworks(null); + } else { + builder.setUnderlyingNetworks(underlyingNetworks.toArray(new Network[0])); + } + + boolean isAlwaysMetered = intent.getBooleanExtra(packageName + ".isAlwaysMetered", false); + builder.setMetered(isAlwaysMetered); + ProxyInfo vpnProxy = intent.getParcelableExtra(packageName + ".httpProxy"); builder.setHttpProxy(vpnProxy); builder.setMtu(MTU); diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java index 17e13478b0..2fc85f6e3e 100755 --- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java +++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java @@ -19,6 +19,7 @@ package com.android.cts.net.hostside; import static android.os.Process.INVALID_UID; import static android.system.OsConstants.*; +import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -31,6 +32,7 @@ import android.net.NetworkRequest; import android.net.Proxy; import android.net.ProxyInfo; import android.net.VpnService; +import android.net.wifi.WifiManager; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.SystemProperties; @@ -61,7 +63,9 @@ import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Random; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** @@ -100,6 +104,7 @@ public class VpnTest extends InstrumentationTestCase { private MyActivity mActivity; private String mPackageName; private ConnectivityManager mCM; + private WifiManager mWifiManager; private RemoteSocketFactoryClient mRemoteSocketFactoryClient; Network mNetwork; @@ -123,7 +128,8 @@ public class VpnTest extends InstrumentationTestCase { mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(), MyActivity.class, null); mPackageName = mActivity.getPackageName(); - mCM = (ConnectivityManager) mActivity.getSystemService(mActivity.CONNECTIVITY_SERVICE); + mCM = (ConnectivityManager) mActivity.getSystemService(Context.CONNECTIVITY_SERVICE); + mWifiManager = (WifiManager) mActivity.getSystemService(Context.WIFI_SERVICE); mRemoteSocketFactoryClient = new RemoteSocketFactoryClient(mActivity); mRemoteSocketFactoryClient.bind(); mDevice.waitForIdle(); @@ -192,9 +198,11 @@ public class VpnTest extends InstrumentationTestCase { } } + // TODO: Consider replacing arguments with a Builder. private void startVpn( String[] addresses, String[] routes, String allowedApplications, - String disallowedApplications, ProxyInfo proxyInfo) throws Exception { + String disallowedApplications, @Nullable ProxyInfo proxyInfo, + @Nullable ArrayList underlyingNetworks, boolean isAlwaysMetered) throws Exception { prepareVpn(); // Register a callback so we will be notified when our VPN comes up. @@ -221,7 +229,10 @@ public class VpnTest extends InstrumentationTestCase { .putExtra(mPackageName + ".routes", TextUtils.join(",", routes)) .putExtra(mPackageName + ".allowedapplications", allowedApplications) .putExtra(mPackageName + ".disallowedapplications", disallowedApplications) - .putExtra(mPackageName + ".httpProxy", proxyInfo); + .putExtra(mPackageName + ".httpProxy", proxyInfo) + .putParcelableArrayListExtra( + mPackageName + ".underlyingNetworks", underlyingNetworks) + .putExtra(mPackageName + ".isAlwaysMetered", isAlwaysMetered); mActivity.startService(intent); synchronized (mLock) { @@ -251,7 +262,8 @@ public class VpnTest extends InstrumentationTestCase { mCallback = new NetworkCallback() { public void onLost(Network network) { synchronized (mLockShutdown) { - Log.i(TAG, "Got lost callback for network=" + network + ",mNetwork = " + mNetwork); + Log.i(TAG, "Got lost callback for network=" + network + + ",mNetwork = " + mNetwork); if( mNetwork == network){ mLockShutdown.notify(); } @@ -574,7 +586,7 @@ public class VpnTest extends InstrumentationTestCase { startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"}, new String[] {"0.0.0.0/0", "::/0"}, - "", "", null); + "", "", null, null /* underlyingNetworks */, false /* isAlwaysMetered */); final Intent intent = receiver.awaitForBroadcast(TimeUnit.MINUTES.toMillis(1)); assertNotNull("Failed to receive broadcast from VPN service", intent); @@ -598,7 +610,7 @@ public class VpnTest extends InstrumentationTestCase { String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName; startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"}, new String[] {"192.0.2.0/24", "2001:db8::/32"}, - allowedApps, "", null); + allowedApps, "", null, null /* underlyingNetworks */, false /* isAlwaysMetered */); assertSocketClosed(fd, TEST_HOST); @@ -620,7 +632,8 @@ public class VpnTest extends InstrumentationTestCase { Log.i(TAG, "Append shell app to disallowedApps: " + disallowedApps); startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"}, new String[] {"192.0.2.0/24", "2001:db8::/32"}, - "", disallowedApps, null); + "", disallowedApps, null, null /* underlyingNetworks */, + false /* isAlwaysMetered */); assertSocketStillOpen(localFd, TEST_HOST); assertSocketStillOpen(remoteFd, TEST_HOST); @@ -657,7 +670,7 @@ public class VpnTest extends InstrumentationTestCase { ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("10.0.0.1", 8888); startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"}, new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", - testProxyInfo); + testProxyInfo, null /* underlyingNetworks */, false /* isAlwaysMetered */); // Check that the proxy change broadcast is received try { @@ -697,7 +710,7 @@ public class VpnTest extends InstrumentationTestCase { ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("10.0.0.1", 8888); startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"}, new String[] {"0.0.0.0/0", "::/0"}, "", disallowedApps, - testProxyInfo); + testProxyInfo, null /* underlyingNetworks */, false /* isAlwaysMetered */); // The disallowed app does has the proxy configs of the default network. assertNetworkHasExpectedProxy(initialProxy, mCM.getActiveNetwork()); @@ -711,7 +724,8 @@ public class VpnTest extends InstrumentationTestCase { proxyBroadcastReceiver.register(); String allowedApps = mPackageName; startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"}, - new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null); + new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null, + null /* underlyingNetworks */, false /* isAlwaysMetered */); try { assertNotNull("No proxy change was broadcast.", @@ -748,7 +762,7 @@ public class VpnTest extends InstrumentationTestCase { proxyBroadcastReceiver.register(); startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"}, new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", - testProxyInfo); + testProxyInfo, null /* underlyingNetworks */, false /* isAlwaysMetered */); assertDefaultProxy(testProxyInfo); mCM.bindProcessToNetwork(initialNetwork); @@ -761,6 +775,145 @@ public class VpnTest extends InstrumentationTestCase { assertDefaultProxy(initialProxy); } + public void testVpnMeterednessWithNoUnderlyingNetwork() throws Exception { + if (!supportedHardware()) { + return; + } + // VPN is not routing any traffic i.e. its underlying networks is an empty array. + ArrayList underlyingNetworks = new ArrayList<>(); + String allowedApps = mPackageName; + + startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"}, + new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null, + underlyingNetworks, false /* isAlwaysMetered */); + + // VPN should now be the active network. + assertEquals(mNetwork, mCM.getActiveNetwork()); + assertVpnTransportContains(NetworkCapabilities.TRANSPORT_VPN); + // VPN with no underlying networks should be metered by default. + assertTrue(isNetworkMetered(mNetwork)); + assertTrue(mCM.isActiveNetworkMetered()); + } + + public void testVpnMeterednessWithNullUnderlyingNetwork() throws Exception { + if (!supportedHardware()) { + return; + } + Network underlyingNetwork = mCM.getActiveNetwork(); + if (underlyingNetwork == null) { + Log.i(TAG, "testVpnMeterednessWithNullUnderlyingNetwork cannot execute" + + " unless there is an active network"); + return; + } + // VPN tracks platform default. + ArrayList underlyingNetworks = null; + String allowedApps = mPackageName; + + startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"}, + new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null, + underlyingNetworks, false /*isAlwaysMetered */); + + // Ensure VPN transports contains underlying network's transports. + assertVpnTransportContains(underlyingNetwork); + // Its meteredness should be same as that of underlying network. + assertEquals(isNetworkMetered(underlyingNetwork), isNetworkMetered(mNetwork)); + // Meteredness based on VPN capabilities and CM#isActiveNetworkMetered should be in sync. + assertEquals(isNetworkMetered(mNetwork), mCM.isActiveNetworkMetered()); + } + + public void testVpnMeterednessWithNonNullUnderlyingNetwork() throws Exception { + if (!supportedHardware()) { + return; + } + Network underlyingNetwork = mCM.getActiveNetwork(); + if (underlyingNetwork == null) { + Log.i(TAG, "testVpnMeterednessWithNonNullUnderlyingNetwork cannot execute" + + " unless there is an active network"); + return; + } + // VPN explicitly declares WiFi to be its underlying network. + ArrayList underlyingNetworks = new ArrayList<>(1); + underlyingNetworks.add(underlyingNetwork); + String allowedApps = mPackageName; + + startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"}, + new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null, + underlyingNetworks, false /* isAlwaysMetered */); + + // Ensure VPN transports contains underlying network's transports. + assertVpnTransportContains(underlyingNetwork); + // Its meteredness should be same as that of underlying network. + assertEquals(isNetworkMetered(underlyingNetwork), isNetworkMetered(mNetwork)); + // Meteredness based on VPN capabilities and CM#isActiveNetworkMetered should be in sync. + assertEquals(isNetworkMetered(mNetwork), mCM.isActiveNetworkMetered()); + } + + public void testAlwaysMeteredVpnWithNullUnderlyingNetwork() throws Exception { + if (!supportedHardware()) { + return; + } + Network underlyingNetwork = mCM.getActiveNetwork(); + if (underlyingNetwork == null) { + Log.i(TAG, "testAlwaysMeteredVpnWithNullUnderlyingNetwork cannot execute" + + " unless there is an active network"); + return; + } + // VPN tracks platform default. + ArrayList underlyingNetworks = null; + String allowedApps = mPackageName; + boolean isAlwaysMetered = true; + + startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"}, + new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null, + underlyingNetworks, isAlwaysMetered); + + // VPN's meteredness does not depend on underlying network since it is always metered. + assertTrue(isNetworkMetered(mNetwork)); + assertTrue(mCM.isActiveNetworkMetered()); + } + + public void testAlwaysMeteredVpnWithNonNullUnderlyingNetwork() throws Exception { + if (!supportedHardware()) { + return; + } + Network underlyingNetwork = mCM.getActiveNetwork(); + if (underlyingNetwork == null) { + Log.i(TAG, "testAlwaysMeteredVpnWithNonNullUnderlyingNetwork cannot execute" + + " unless there is an active network"); + return; + } + // VPN explicitly declares its underlying network. + ArrayList underlyingNetworks = new ArrayList<>(1); + underlyingNetworks.add(underlyingNetwork); + String allowedApps = mPackageName; + boolean isAlwaysMetered = true; + + startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"}, + new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null, + underlyingNetworks, isAlwaysMetered); + + // VPN's meteredness does not depend on underlying network since it is always metered. + assertTrue(isNetworkMetered(mNetwork)); + assertTrue(mCM.isActiveNetworkMetered()); + } + + private boolean isNetworkMetered(Network network) { + NetworkCapabilities nc = mCM.getNetworkCapabilities(network); + return !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + } + + private void assertVpnTransportContains(Network underlyingNetwork) { + int[] transports = mCM.getNetworkCapabilities(underlyingNetwork).getTransportTypes(); + assertVpnTransportContains(transports); + } + + private void assertVpnTransportContains(int... transports) { + NetworkCapabilities vpnCaps = mCM.getNetworkCapabilities(mNetwork); + for (int transport : transports) { + assertTrue(vpnCaps.hasTransport(transport)); + } + } + private void assertDefaultProxy(ProxyInfo expected) { assertEquals("Incorrect proxy config.", expected, mCM.getDefaultProxy()); String expectedHost = expected == null ? null : expected.getHost(); diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java index e34ee89897..6e37a24c68 100644 --- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java +++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java @@ -64,4 +64,31 @@ public class HostsideVpnTests extends HostsideNetworkTestCase { public void testBindToNetworkWithProxy() throws Exception { runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBindToNetworkWithProxy"); } + + public void testVpnMeterednessWithNoUnderlyingNetwork() throws Exception { + runDeviceTests( + TEST_PKG, TEST_PKG + ".VpnTest", "testVpnMeterednessWithNoUnderlyingNetwork"); + } + + public void testVpnMeterednessWithNullUnderlyingNetwork() throws Exception { + runDeviceTests( + TEST_PKG, TEST_PKG + ".VpnTest", "testVpnMeterednessWithNullUnderlyingNetwork"); + } + + public void testVpnMeterednessWithNonNullUnderlyingNetwork() throws Exception { + runDeviceTests( + TEST_PKG, TEST_PKG + ".VpnTest", "testVpnMeterednessWithNonNullUnderlyingNetwork"); + } + + public void testAlwaysMeteredVpnWithNullUnderlyingNetwork() throws Exception { + runDeviceTests( + TEST_PKG, TEST_PKG + ".VpnTest", "testAlwaysMeteredVpnWithNullUnderlyingNetwork"); + } + + public void testAlwaysMeteredVpnWithNonNullUnderlyingNetwork() throws Exception { + runDeviceTests( + TEST_PKG, + TEST_PKG + ".VpnTest", + "testAlwaysMeteredVpnWithNonNullUnderlyingNetwork"); + } }