[automerger skipped] Merge changes from topic "am-6bf6d04b-31af-4d11-9286-c78baea6dc7f" into oc-dev am: 704a9a46d8 -s ours am: ecaabe8f3e -s ours am: 9b319aa0ef -s ours
am: e6bf3b799e -s ours
am skip reason: change_id I91b4ab018a9e7fc73dcb7969e4a6520d6b27d629 with SHA1 d51ae5760d is in history
Change-Id: I57a24748b4bb6abaa50a9ff43fffb235ce54d6c1
diff --git a/tests/cts/hostside/AndroidTest.xml b/tests/cts/hostside/AndroidTest.xml
index 0656cae..c7cab7b 100644
--- a/tests/cts/hostside/AndroidTest.xml
+++ b/tests/cts/hostside/AndroidTest.xml
@@ -16,6 +16,9 @@
<configuration description="Config for CTS net host test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
<target_preparer class="com.android.cts.net.NetPolicyTestsPreparer" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/tests/cts/hostside/OWNERS b/tests/cts/hostside/OWNERS
new file mode 100644
index 0000000..52c8053
--- /dev/null
+++ b/tests/cts/hostside/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 61373
+sudheersai@google.com
+lorenzo@google.com
+jchalard@google.com
diff --git a/tests/cts/hostside/aidl/Android.mk b/tests/cts/hostside/aidl/Android.mk
index 85f71c3..20dabc1 100644
--- a/tests/cts/hostside/aidl/Android.mk
+++ b/tests/cts/hostside/aidl/Android.mk
@@ -19,6 +19,7 @@
LOCAL_SDK_VERSION := current
LOCAL_SRC_FILES := \
com/android/cts/net/hostside/IMyService.aidl \
+ com/android/cts/net/hostside/INetworkCallback.aidl \
com/android/cts/net/hostside/INetworkStateObserver.aidl \
com/android/cts/net/hostside/IRemoteSocketFactory.aidl
LOCAL_MODULE := CtsHostsideNetworkTestsAidl
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
index 72d1059..a820ae5 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
+++ b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
@@ -16,10 +16,13 @@
package com.android.cts.net.hostside;
+import com.android.cts.net.hostside.INetworkCallback;
+
interface IMyService {
void registerBroadcastReceiver();
int getCounters(String receiverName, String action);
String checkNetworkStatus();
String getRestrictBackgroundStatus();
void sendNotification(int notificationId, String notificationType);
+ void registerNetworkCallback(in INetworkCallback cb);
}
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkCallback.aidl b/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkCallback.aidl
new file mode 100644
index 0000000..740ec26
--- /dev/null
+++ b/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkCallback.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net.hostside;
+
+import android.net.Network;
+
+interface INetworkCallback {
+ void onBlockedStatusChanged(in Network network, boolean blocked);
+ void onAvailable(in Network network);
+ void onLost(in Network network);
+}
diff --git a/tests/cts/hostside/app/Android.mk b/tests/cts/hostside/app/Android.mk
index 6d89e58..11f6bb1 100644
--- a/tests/cts/hostside/app/Android.mk
+++ b/tests/cts/hostside/app/Android.mk
@@ -19,7 +19,8 @@
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
+#LOCAL_SDK_VERSION := current
+LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util-axt ctstestrunner-axt ub-uiautomator \
CtsHostsideNetworkTestsAidl
diff --git a/tests/cts/hostside/app/AndroidManifest.xml b/tests/cts/hostside/app/AndroidManifest.xml
index ba0e242..f54b5c1 100644
--- a/tests/cts/hostside/app/AndroidManifest.xml
+++ b/tests/cts/hostside/app/AndroidManifest.xml
@@ -23,6 +23,8 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application>
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
index 1f5984a..55bd406 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
@@ -180,6 +180,25 @@
assertBackgroundNetworkAccess(true);
}
+ public void testAppIdleNetworkAccess_idleWhitelisted() throws Exception {
+ if (!isSupported()) return;
+
+ setAppIdle(true);
+ assertAppIdle(true);
+ assertBackgroundNetworkAccess(false);
+
+ addAppIdleWhitelist(mUid);
+ assertBackgroundNetworkAccess(true);
+
+ removeAppIdleWhitelist(mUid);
+ assertBackgroundNetworkAccess(false);
+
+ // Make sure whitelisting a random app doesn't affect the tested app.
+ addAppIdleWhitelist(mUid + 1);
+ assertBackgroundNetworkAccess(false);
+ removeAppIdleWhitelist(mUid + 1);
+ }
+
public void testAppIdle_toast() throws Exception {
if (!isSupported()) return;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 1844878..40d7e34 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -26,10 +26,6 @@
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.app.NotificationManager;
@@ -48,6 +44,7 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.test.InstrumentationTestCase;
@@ -56,6 +53,10 @@
import com.android.compatibility.common.util.BatteryUtils;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
/**
* Superclass for tests related to background network restrictions.
*/
@@ -95,7 +96,8 @@
private static final String NETWORK_STATUS_SEPARATOR = "\\|";
private static final int SECOND_IN_MS = 1000;
static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
- private static final int PROCESS_STATE_FOREGROUND_SERVICE = 3;
+ private static int PROCESS_STATE_FOREGROUND_SERVICE;
+
private static final int PROCESS_STATE_TOP = 2;
private static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
@@ -134,6 +136,8 @@
protected void setUp() throws Exception {
super.setUp();
+ PROCESS_STATE_FOREGROUND_SERVICE = (Integer) ActivityManager.class
+ .getDeclaredField("PROCESS_STATE_FOREGROUND_SERVICE").get(null);
mInstrumentation = getInstrumentation();
mContext = mInstrumentation.getContext();
mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -153,7 +157,14 @@
Log.i(TAG, "Apps status on " + getName() + ":\n"
+ "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
+ "\tapp2: uid=" + mUid + ", state=" + getProcessStateByUid(mUid));
- executeShellCommand("settings get global app_idle_constants");
+
+ // app_idle_constants set in NetPolicyTestsPreparer.setUp() is not always sucessful (suspect
+ // timing issue), here we set it again to make sure.
+ final String appIdleConstants = "parole_duration=0,stable_charging_threshold=0";
+ executeShellCommand("settings put global app_idle_constants " + appIdleConstants);
+ final String currentConstants =
+ executeShellCommand("settings get global app_idle_constants");
+ assertEquals(appIdleConstants, currentConstants);
}
@Override
@@ -204,7 +215,7 @@
do {
attempts++;
count = getNumberBroadcastsReceived(receiverName, ACTION_RESTRICT_BACKGROUND_CHANGED);
- if (count == expectedCount) {
+ if (count >= expectedCount) {
break;
}
Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after "
@@ -304,7 +315,7 @@
return mSupported;
}
- protected boolean isBatterySaverSupported() throws Exception {
+ protected boolean isBatterySaverSupported() {
return BatteryUtils.isBatterySaverSupported();
}
@@ -377,6 +388,23 @@
}
/**
+ * As per CDD requirements, if the device doesn't support data saver mode then
+ * ConnectivityManager.getRestrictBackgroundStatus() will always return
+ * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
+ * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
+ * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
+ */
+ protected boolean isDataSaverSupported() throws Exception {
+ assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
+ try {
+ setRestrictBackground(true);
+ return !isMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
+ } finally {
+ setRestrictBackground(false);
+ }
+ }
+
+ /**
* Returns whether an app state should be considered "background" for restriction purposes.
*/
protected boolean isBackground(int state) {
@@ -751,6 +779,20 @@
assertRestrictBackground("restrict-background-blacklist", uid, expected);
}
+ protected void addAppIdleWhitelist(int uid) throws Exception {
+ executeShellCommand("cmd netpolicy add app-idle-whitelist " + uid);
+ assertAppIdleWhitelist(uid, true);
+ }
+
+ protected void removeAppIdleWhitelist(int uid) throws Exception {
+ executeShellCommand("cmd netpolicy remove app-idle-whitelist " + uid);
+ assertAppIdleWhitelist(uid, false);
+ }
+
+ protected void assertAppIdleWhitelist(int uid, boolean expected) throws Exception {
+ assertRestrictBackground("app-idle-whitelist", uid, expected);
+ }
+
private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception {
final int maxTries = 5;
boolean actual = false;
@@ -969,6 +1011,10 @@
fail("app2 receiver is not ready");
}
+ protected void registerNetworkCallback(INetworkCallback cb) throws Exception {
+ mServiceClient.registerNetworkCallback(cb);
+ }
+
/**
* Registers a {@link NotificationListenerService} implementation that will execute the
* notification actions right after the notification is sent.
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
index 599a31c..cfe6a73 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
@@ -22,6 +22,8 @@
import android.util.Log;
+import com.android.compatibility.common.util.CddTest;
+
public class DataSaverModeTest extends AbstractRestrictBackgroundNetworkTestCase {
private static final String[] REQUIRED_WHITELISTED_PACKAGES = {
@@ -35,7 +37,6 @@
super.setUp();
mIsDataSaverSupported = isDataSaverSupported();
- if (!isSupported()) return;
// Set initial state.
setRestrictBackground(false);
@@ -73,23 +74,6 @@
return mIsDataSaverSupported && super.isSupported();
}
- /**
- * As per CDD requirements, if the device doesn't support data saver mode then
- * ConnectivityManager.getRestrictBackgroundStatus() will always return
- * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
- * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
- * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
- */
- private boolean isDataSaverSupported() throws Exception {
- assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
- try {
- setRestrictBackground(true);
- return !isMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
- } finally {
- setRestrictBackground(false);
- }
- }
-
public void testGetRestrictBackgroundStatus_disabled() throws Exception {
if (!isSupported()) return;
@@ -218,6 +202,20 @@
}
}
+ @CddTest(requirement="7.4.7/C-2-2")
+ public void testBroadcastNotSentOnUnsupportedDevices() throws Exception {
+ if (isSupported()) return;
+
+ setRestrictBackground(true);
+ assertRestrictBackgroundChangedReceived(0);
+
+ setRestrictBackground(false);
+ assertRestrictBackgroundChangedReceived(0);
+
+ setRestrictBackground(true);
+ assertRestrictBackgroundChangedReceived(0);
+ }
+
private void assertDataSaverStatusOnBackground(int expectedStatus) throws Exception {
assertRestrictBackgroundStatus(expectedStatus);
assertBackgroundNetworkAccess(expectedStatus != RESTRICT_BACKGROUND_STATUS_ENABLED);
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MixedModesTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MixedModesTest.java
index 7f65b55..b1a2186 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MixedModesTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MixedModesTest.java
@@ -326,4 +326,89 @@
setBatterySaverMode(false);
}
}
+
+ /**
+ * Tests that the app idle whitelist works as expected when doze and appIdle mode are enabled.
+ */
+ public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
+ if (!isSupported()) {
+ return;
+ }
+
+ setDozeMode(true);
+ setAppIdle(true);
+
+ try {
+ assertBackgroundNetworkAccess(false);
+
+ // UID still shouldn't have access because of Doze.
+ addAppIdleWhitelist(mUid);
+ assertBackgroundNetworkAccess(false);
+
+ removeAppIdleWhitelist(mUid);
+ assertBackgroundNetworkAccess(false);
+ } finally {
+ setAppIdle(false);
+ setDozeMode(false);
+ }
+ }
+
+ public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
+ if (!isSupported()) {
+ return;
+ }
+
+ setDozeMode(true);
+ setAppIdle(true);
+
+ try {
+ assertBackgroundNetworkAccess(false);
+
+ addAppIdleWhitelist(mUid);
+ assertBackgroundNetworkAccess(false);
+
+ addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
+ assertBackgroundNetworkAccess(true);
+
+ // Wait until the whitelist duration is expired.
+ SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
+ assertBackgroundNetworkAccess(false);
+ } finally {
+ setAppIdle(false);
+ setDozeMode(false);
+ removeAppIdleWhitelist(mUid);
+ }
+ }
+
+ public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
+ if (!isBatterySaverSupported()) {
+ Log.i(TAG, "Skipping " + getClass() + "." + getName()
+ + "() because device does not support Battery saver mode");
+ return;
+ }
+ if (!isSupported()) {
+ return;
+ }
+
+ setBatterySaverMode(true);
+ setAppIdle(true);
+
+ try {
+ assertBackgroundNetworkAccess(false);
+
+ addAppIdleWhitelist(mUid);
+ assertBackgroundNetworkAccess(false);
+
+ addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
+ assertBackgroundNetworkAccess(true);
+
+ // Wait until the whitelist duration is expired.
+ SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
+ assertBackgroundNetworkAccess(false);
+ } finally {
+ setAppIdle(false);
+ setBatterySaverMode(false);
+ removeAppIdleWhitelist(mUid);
+ }
+ }
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
index e2976c2..3ee7b99 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
@@ -26,8 +26,6 @@
import com.android.cts.net.hostside.IMyService;
-import java.io.FileDescriptor;
-
public class MyServiceClient {
private static final int TIMEOUT_MS = 5000;
private static final String PACKAGE = MyServiceClient.class.getPackage().getName();
@@ -98,4 +96,8 @@
public void sendNotification(int notificationId, String notificationType) throws RemoteException {
mService.sendNotification(notificationId, notificationType);
}
+
+ public void registerNetworkCallback(INetworkCallback cb) throws RemoteException {
+ mService.registerNetworkCallback(cb);
+ }
}
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 90a3ce4..7d3d4fc 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,8 @@
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;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -26,12 +28,17 @@
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.ArrayList;
public class MyVpnService extends VpnService {
private static String TAG = "MyVpnService";
private static int MTU = 1799;
+ public static final String ACTION_ESTABLISHED = "com.android.cts.net.hostside.ESTABNLISHED";
+ public static final String EXTRA_ALWAYS_ON = "is-always-on";
+ public static final String EXTRA_LOCKDOWN_ENABLED = "is-lockdown-enabled";
+
private ParcelFileDescriptor mFd = null;
private PacketReflector mPacketReflector = null;
@@ -113,6 +120,20 @@
}
}
+ ArrayList<Network> 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);
builder.setBlocking(true);
builder.setSession("MyVpnService");
@@ -126,10 +147,19 @@
mFd = builder.establish();
Log.i(TAG, "Established, fd=" + (mFd == null ? "null" : mFd.getFd()));
+ broadcastEstablished();
+
mPacketReflector = new PacketReflector(mFd.getFileDescriptor(), MTU);
mPacketReflector.start();
}
+ private void broadcastEstablished() {
+ final Intent bcIntent = new Intent(ACTION_ESTABLISHED);
+ bcIntent.putExtra(EXTRA_ALWAYS_ON, isAlwaysOn());
+ bcIntent.putExtra(EXTRA_LOCKDOWN_ENABLED, isLockdownEnabled());
+ sendBroadcast(bcIntent);
+ }
+
private void stop() {
if (mPacketReflector != null) {
mPacketReflector.interrupt();
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
new file mode 100644
index 0000000..24dde9d
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net.hostside;
+
+import android.net.Network;
+
+import java.util.Objects;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class NetworkCallbackTest extends AbstractRestrictBackgroundNetworkTestCase {
+
+ private boolean mIsDataSaverSupported;
+ private Network mNetwork;
+ private final TestNetworkCallback mTestNetworkCallback = new TestNetworkCallback();
+
+ enum CallbackState {
+ NONE,
+ AVAILABLE,
+ LOST,
+ BLOCKED_STATUS
+ }
+
+ private static class CallbackInfo {
+ public final CallbackState state;
+ public final Network network;
+ public final Object arg;
+
+ CallbackInfo(CallbackState s, Network n, Object o) {
+ state = s; network = n; arg = o;
+ }
+
+ public String toString() {
+ return String.format("%s (%s) (%s)", state, network, arg);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof CallbackInfo)) return false;
+ // Ignore timeMs, since it's unpredictable.
+ final CallbackInfo other = (CallbackInfo) o;
+ return (state == other.state) && Objects.equals(network, other.network)
+ && Objects.equals(arg, other.arg);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(state, network, arg);
+ }
+ }
+
+ private class TestNetworkCallback extends INetworkCallback.Stub {
+ private static final int TEST_CALLBACK_TIMEOUT_MS = 200;
+
+ private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
+
+ protected void setLastCallback(CallbackState state, Network network, Object o) {
+ mCallbacks.offer(new CallbackInfo(state, network, o));
+ }
+
+ CallbackInfo nextCallback(int timeoutMs) {
+ CallbackInfo cb = null;
+ try {
+ cb = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ }
+ if (cb == null) {
+ fail("Did not receive callback after " + timeoutMs + "ms");
+ }
+ return cb;
+ }
+
+ CallbackInfo expectCallback(CallbackState state, Network expectedNetwork, Object o) {
+ final CallbackInfo expected = new CallbackInfo(state, expectedNetwork, o);
+ final CallbackInfo actual = nextCallback(TEST_CALLBACK_TIMEOUT_MS);
+ assertEquals("Unexpected callback:", expected, actual);
+ return actual;
+ }
+
+ @Override
+ public void onAvailable(Network network) {
+ setLastCallback(CallbackState.AVAILABLE, network, null);
+ }
+
+ @Override
+ public void onLost(Network network) {
+ setLastCallback(CallbackState.LOST, network, null);
+ }
+
+ @Override
+ public void onBlockedStatusChanged(Network network, boolean blocked) {
+ setLastCallback(CallbackState.BLOCKED_STATUS, network, blocked);
+ }
+
+ public void expectLostCallback(Network expectedNetwork) {
+ expectCallback(CallbackState.LOST, expectedNetwork, null);
+ }
+
+ public void expectAvailableCallback(Network expectedNetwork) {
+ expectCallback(CallbackState.AVAILABLE, expectedNetwork, null);
+ }
+
+ public void expectBlockedStatusCallback(Network expectedNetwork, boolean expectBlocked) {
+ expectCallback(CallbackState.BLOCKED_STATUS, expectedNetwork,
+ expectBlocked);
+ }
+
+ void assertNoCallback() {
+ CallbackInfo cb = null;
+ try {
+ cb = mCallbacks.poll(TEST_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // Expected.
+ }
+ if (cb != null) {
+ assertNull("Unexpected callback: " + cb, cb);
+ }
+ }
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mIsDataSaverSupported = isDataSaverSupported();
+
+ mNetwork = mCm.getActiveNetwork();
+
+ // Set initial state.
+ setBatterySaverMode(false);
+ registerBroadcastReceiver();
+
+ if (!mIsDataSaverSupported) return;
+ setRestrictBackground(false);
+ removeRestrictBackgroundWhitelist(mUid);
+ removeRestrictBackgroundBlacklist(mUid);
+ assertRestrictBackgroundChangedReceived(0);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ if (!mIsDataSaverSupported) return;
+
+ try {
+ resetMeteredNetwork();
+ } finally {
+ setRestrictBackground(false);
+ }
+ }
+
+ public void testOnBlockedStatusChanged_data_saver() throws Exception {
+ if (!mIsDataSaverSupported) return;
+
+ // Prepare metered wifi
+ if (!setMeteredNetwork()) return;
+
+ // Register callback
+ registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
+ mTestNetworkCallback.expectAvailableCallback(mNetwork);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+ // Enable restrict background
+ setRestrictBackground(true);
+ assertBackgroundNetworkAccess(false);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+
+ // Add to whitelist
+ addRestrictBackgroundWhitelist(mUid);
+ assertBackgroundNetworkAccess(true);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+ // Remove from whitelist
+ removeRestrictBackgroundWhitelist(mUid);
+ assertBackgroundNetworkAccess(false);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+
+ // Set to non-metered network
+ setUnmeteredNetwork();
+ assertBackgroundNetworkAccess(true);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+ // Disable restrict background, should not trigger callback
+ setRestrictBackground(false);
+ assertBackgroundNetworkAccess(true);
+ mTestNetworkCallback.assertNoCallback();
+ }
+
+
+ public void testOnBlockedStatusChanged_power_saver() throws Exception {
+ // Prepare metered wifi
+ if (!setMeteredNetwork()) return;
+
+ // Register callback
+ registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
+ mTestNetworkCallback.expectAvailableCallback(mNetwork);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+ // Enable Power Saver
+ setBatterySaverMode(true);
+ assertBackgroundNetworkAccess(false);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+
+ // Disable Power Saver
+ setBatterySaverMode(false);
+ assertBackgroundNetworkAccess(true);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+ // Set to non-metered network
+ setUnmeteredNetwork();
+ mTestNetworkCallback.assertNoCallback();
+
+ // Enable Power Saver
+ setBatterySaverMode(true);
+ assertBackgroundNetworkAccess(false);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+
+ // Disable Power Saver
+ setBatterySaverMode(false);
+ assertBackgroundNetworkAccess(true);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+ }
+
+ // TODO: 1. test against VPN lockdown.
+ // 2. test against multiple networks.
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java
index a4a2956..124c2c3 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java
@@ -16,6 +16,11 @@
package com.android.cts.net.hostside;
+import static android.system.OsConstants.ICMP6_ECHO_REPLY;
+import static android.system.OsConstants.ICMP6_ECHO_REQUEST;
+import static android.system.OsConstants.ICMP_ECHO;
+import static android.system.OsConstants.ICMP_ECHOREPLY;
+
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
@@ -47,8 +52,6 @@
private static final byte ICMP_ECHO = 8;
private static final byte ICMP_ECHOREPLY = 0;
- private static final byte ICMPV6_ECHO_REQUEST = (byte) 128;
- private static final byte ICMPV6_ECHO_REPLY = (byte) 129;
private static String TAG = "PacketReflector";
@@ -125,7 +128,7 @@
byte type = buf[hdrLen];
if (!(version == 4 && type == ICMP_ECHO) &&
- !(version == 6 && type == ICMPV6_ECHO_REQUEST)) {
+ !(version == 6 && type == (byte) ICMP6_ECHO_REQUEST)) {
return;
}
@@ -145,10 +148,18 @@
return;
}
+ byte replyType = buf[hdrLen];
+ if ((type == ICMP_ECHO && replyType != ICMP_ECHOREPLY)
+ || (type == (byte) ICMP6_ECHO_REQUEST && replyType != (byte) ICMP6_ECHO_REPLY)) {
+ Log.i(TAG, "Received unexpected ICMP reply: original " + type
+ + ", reply " + replyType);
+ return;
+ }
+
// Compare the response we got with the original packet.
// The only thing that should have changed are addresses, type and checksum.
// Overwrite them with the received bytes and see if the packet is otherwise identical.
- request[hdrLen] = buf[hdrLen]; // Type.
+ request[hdrLen] = buf[hdrLen]; // Type
request[hdrLen + 2] = buf[hdrLen + 2]; // Checksum byte 1.
request[hdrLen + 3] = buf[hdrLen + 3]; // Checksum byte 2.
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 bc982ce..2fc85f6 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
@@ -16,8 +16,11 @@
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;
import android.net.ConnectivityManager;
@@ -26,34 +29,32 @@
import android.net.Network;
import android.net.NetworkCapabilities;
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;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.UiScrollable;
import android.support.test.uiautomator.UiSelector;
import android.system.ErrnoException;
import android.system.Os;
+import android.system.OsConstants;
import android.system.StructPollfd;
import android.test.InstrumentationTestCase;
import android.test.MoreAsserts;
import android.text.TextUtils;
import android.util.Log;
-import com.android.cts.net.hostside.IRemoteSocketFactory;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
-import java.io.BufferedReader;
import java.io.Closeable;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.FileInputStream;
-import java.io.InputStreamReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.io.PrintWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet6Address;
@@ -61,9 +62,11 @@
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
-import java.net.SocketException;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Tests for the VpnService API.
@@ -101,6 +104,7 @@
private MyActivity mActivity;
private String mPackageName;
private ConnectivityManager mCM;
+ private WifiManager mWifiManager;
private RemoteSocketFactoryClient mRemoteSocketFactoryClient;
Network mNetwork;
@@ -124,7 +128,8 @@
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();
@@ -193,10 +198,11 @@
}
}
+ // TODO: Consider replacing arguments with a Builder.
private void startVpn(
- String[] addresses, String[] routes,
- String allowedApplications, String disallowedApplications) throws Exception {
-
+ String[] addresses, String[] routes, String allowedApplications,
+ String disallowedApplications, @Nullable ProxyInfo proxyInfo,
+ @Nullable ArrayList<Network> underlyingNetworks, boolean isAlwaysMetered) throws Exception {
prepareVpn();
// Register a callback so we will be notified when our VPN comes up.
@@ -222,7 +228,12 @@
.putExtra(mPackageName + ".addresses", TextUtils.join(",", addresses))
.putExtra(mPackageName + ".routes", TextUtils.join(",", routes))
.putExtra(mPackageName + ".allowedapplications", allowedApplications)
- .putExtra(mPackageName + ".disallowedapplications", disallowedApplications);
+ .putExtra(mPackageName + ".disallowedapplications", disallowedApplications)
+ .putExtra(mPackageName + ".httpProxy", proxyInfo)
+ .putParcelableArrayListExtra(
+ mPackageName + ".underlyingNetworks", underlyingNetworks)
+ .putExtra(mPackageName + ".isAlwaysMetered", isAlwaysMetered);
+
mActivity.startService(intent);
synchronized (mLock) {
if (mNetwork == null) {
@@ -251,7 +262,8 @@
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();
}
@@ -353,7 +365,7 @@
MoreAsserts.assertEquals(data, read);
}
- private static void checkTcpReflection(String to, String expectedFrom) throws IOException {
+ private void checkTcpReflection(String to, String expectedFrom) throws IOException {
// Exercise TCP over the VPN by "connecting to ourselves". We open a server socket and a
// client socket, and connect the client socket to a remote host, with the port of the
// server socket. The PacketReflector reflects the packets, changing the source addresses
@@ -391,7 +403,8 @@
// Accept the connection on the server side.
listen.setSoTimeout(SOCKET_TIMEOUT_MS);
server = listen.accept();
-
+ checkConnectionOwnerUidTcp(client);
+ checkConnectionOwnerUidTcp(server);
// Check that the source and peer addresses are as expected.
assertEquals(expectedFrom, client.getLocalAddress().getHostAddress());
assertEquals(expectedFrom, server.getLocalAddress().getHostAddress());
@@ -424,7 +437,23 @@
}
}
- private static void checkUdpEcho(String to, String expectedFrom) throws IOException {
+ private void checkConnectionOwnerUidUdp(DatagramSocket s, boolean expectSuccess) {
+ final int expectedUid = expectSuccess ? Process.myUid() : INVALID_UID;
+ InetSocketAddress loc = new InetSocketAddress(s.getLocalAddress(), s.getLocalPort());
+ InetSocketAddress rem = new InetSocketAddress(s.getInetAddress(), s.getPort());
+ int uid = mCM.getConnectionOwnerUid(OsConstants.IPPROTO_UDP, loc, rem);
+ assertEquals(expectedUid, uid);
+ }
+
+ private void checkConnectionOwnerUidTcp(Socket s) {
+ final int expectedUid = Process.myUid();
+ InetSocketAddress loc = new InetSocketAddress(s.getLocalAddress(), s.getLocalPort());
+ InetSocketAddress rem = new InetSocketAddress(s.getInetAddress(), s.getPort());
+ int uid = mCM.getConnectionOwnerUid(OsConstants.IPPROTO_TCP, loc, rem);
+ assertEquals(expectedUid, uid);
+ }
+
+ private void checkUdpEcho(String to, String expectedFrom) throws IOException {
DatagramSocket s;
InetAddress address = InetAddress.getByName(to);
if (address instanceof Inet6Address) { // http://b/18094870
@@ -448,6 +477,7 @@
try {
if (expectedFrom != null) {
s.send(p);
+ checkConnectionOwnerUidUdp(s, true);
s.receive(p);
MoreAsserts.assertEquals(data, p.getData());
} else {
@@ -455,7 +485,9 @@
s.send(p);
s.receive(p);
fail("Received unexpected reply");
- } catch(IOException expected) {}
+ } catch (IOException expected) {
+ checkConnectionOwnerUidUdp(s, false);
+ }
}
} finally {
s.close();
@@ -537,16 +569,36 @@
public void testDefault() throws Exception {
if (!supportedHardware()) return;
+ // If adb TCP port opened, this test may running by adb over network.
+ // All of socket would be destroyed in this test. So this test don't
+ // support adb over network, see b/119382723.
+ if (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1
+ || SystemProperties.getInt("service.adb.tcp.port", -1) > -1) {
+ Log.i(TAG, "adb is running over the network, so skip this test");
+ return;
+ }
+
+ final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(
+ getInstrumentation().getTargetContext(), MyVpnService.ACTION_ESTABLISHED);
+ receiver.register();
FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
new String[] {"0.0.0.0/0", "::/0"},
- "", "");
+ "", "", null, null /* underlyingNetworks */, false /* isAlwaysMetered */);
+
+ final Intent intent = receiver.awaitForBroadcast(TimeUnit.MINUTES.toMillis(1));
+ assertNotNull("Failed to receive broadcast from VPN service", intent);
+ assertFalse("Wrong VpnService#isAlwaysOn",
+ intent.getBooleanExtra(MyVpnService.EXTRA_ALWAYS_ON, true));
+ assertFalse("Wrong VpnService#isLockdownEnabled",
+ intent.getBooleanExtra(MyVpnService.EXTRA_LOCKDOWN_ENABLED, true));
assertSocketClosed(fd, TEST_HOST);
checkTrafficOnVpn();
+ receiver.unregisterQuietly();
}
public void testAppAllowed() throws Exception {
@@ -554,10 +606,11 @@
FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
+ // Shell app must not be put in here or it would kill the ADB-over-network use case
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, "");
+ allowedApps, "", null, null /* underlyingNetworks */, false /* isAlwaysMetered */);
assertSocketClosed(fd, TEST_HOST);
@@ -571,13 +624,329 @@
FileDescriptor remoteFd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
String disallowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
+ // If adb TCP port opened, this test may running by adb over TCP.
+ // Add com.android.shell appllication into blacklist to exclude adb socket for VPN test,
+ // see b/119382723.
+ // Note: The test don't support running adb over network for root device
+ disallowedApps = disallowedApps + ",com.android.shell";
+ 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);
+ "", disallowedApps, null, null /* underlyingNetworks */,
+ false /* isAlwaysMetered */);
assertSocketStillOpen(localFd, TEST_HOST);
assertSocketStillOpen(remoteFd, TEST_HOST);
checkNoTrafficOnVpn();
}
+
+ public void testGetConnectionOwnerUidSecurity() throws Exception {
+ if (!supportedHardware()) return;
+
+ DatagramSocket s;
+ InetAddress address = InetAddress.getByName("localhost");
+ s = new DatagramSocket();
+ s.setSoTimeout(SOCKET_TIMEOUT_MS);
+ s.connect(address, 7);
+ InetSocketAddress loc = new InetSocketAddress(s.getLocalAddress(), s.getLocalPort());
+ InetSocketAddress rem = new InetSocketAddress(s.getInetAddress(), s.getPort());
+ try {
+ int uid = mCM.getConnectionOwnerUid(OsConstants.IPPROTO_TCP, loc, rem);
+ fail("Only an active VPN app may call this API.");
+ } catch (SecurityException expected) {
+ return;
+ }
+ }
+
+ public void testSetProxy() throws Exception {
+ if (!supportedHardware()) return;
+ ProxyInfo initialProxy = mCM.getDefaultProxy();
+ // Receiver for the proxy change broadcast.
+ BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
+ proxyBroadcastReceiver.register();
+
+ String allowedApps = mPackageName;
+ 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, null /* underlyingNetworks */, false /* isAlwaysMetered */);
+
+ // Check that the proxy change broadcast is received
+ try {
+ assertNotNull("No proxy change was broadcast when VPN is connected.",
+ proxyBroadcastReceiver.awaitForBroadcast());
+ } finally {
+ proxyBroadcastReceiver.unregisterQuietly();
+ }
+
+ // Proxy is set correctly in network and in link properties.
+ assertNetworkHasExpectedProxy(testProxyInfo, mNetwork);
+ assertDefaultProxy(testProxyInfo);
+
+ proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
+ proxyBroadcastReceiver.register();
+ stopVpn();
+ try {
+ assertNotNull("No proxy change was broadcast when VPN was disconnected.",
+ proxyBroadcastReceiver.awaitForBroadcast());
+ } finally {
+ proxyBroadcastReceiver.unregisterQuietly();
+ }
+
+ // After disconnecting from VPN, the proxy settings are the ones of the initial network.
+ assertDefaultProxy(initialProxy);
+ }
+
+ public void testSetProxyDisallowedApps() throws Exception {
+ if (!supportedHardware()) return;
+ ProxyInfo initialProxy = mCM.getDefaultProxy();
+
+ // If adb TCP port opened, this test may running by adb over TCP.
+ // Add com.android.shell appllication into blacklist to exclude adb socket for VPN test,
+ // see b/119382723.
+ // Note: The test don't support running adb over network for root device
+ String disallowedApps = mPackageName + ",com.android.shell";
+ 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, null /* underlyingNetworks */, false /* isAlwaysMetered */);
+
+ // The disallowed app does has the proxy configs of the default network.
+ assertNetworkHasExpectedProxy(initialProxy, mCM.getActiveNetwork());
+ assertDefaultProxy(initialProxy);
+ }
+
+ public void testNoProxy() throws Exception {
+ if (!supportedHardware()) return;
+ ProxyInfo initialProxy = mCM.getDefaultProxy();
+ BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
+ 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,
+ null /* underlyingNetworks */, false /* isAlwaysMetered */);
+
+ try {
+ assertNotNull("No proxy change was broadcast.",
+ proxyBroadcastReceiver.awaitForBroadcast());
+ } finally {
+ proxyBroadcastReceiver.unregisterQuietly();
+ }
+
+ // The VPN network has no proxy set.
+ assertNetworkHasExpectedProxy(null, mNetwork);
+
+ proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
+ proxyBroadcastReceiver.register();
+ stopVpn();
+ try {
+ assertNotNull("No proxy change was broadcast.",
+ proxyBroadcastReceiver.awaitForBroadcast());
+ } finally {
+ proxyBroadcastReceiver.unregisterQuietly();
+ }
+ // After disconnecting from VPN, the proxy settings are the ones of the initial network.
+ assertDefaultProxy(initialProxy);
+ assertNetworkHasExpectedProxy(initialProxy, mCM.getActiveNetwork());
+ }
+
+ public void testBindToNetworkWithProxy() throws Exception {
+ if (!supportedHardware()) return;
+ String allowedApps = mPackageName;
+ Network initialNetwork = mCM.getActiveNetwork();
+ ProxyInfo initialProxy = mCM.getDefaultProxy();
+ ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("10.0.0.1", 8888);
+ // Receiver for the proxy change broadcast.
+ BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
+ 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, null /* underlyingNetworks */, false /* isAlwaysMetered */);
+
+ assertDefaultProxy(testProxyInfo);
+ mCM.bindProcessToNetwork(initialNetwork);
+ try {
+ assertNotNull("No proxy change was broadcast.",
+ proxyBroadcastReceiver.awaitForBroadcast());
+ } finally {
+ proxyBroadcastReceiver.unregisterQuietly();
+ }
+ 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<Network> 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<Network> 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<Network> 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<Network> 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<Network> 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();
+ String expectedPort = expected == null ? null : String.valueOf(expected.getPort());
+ assertEquals("Incorrect proxy host system property.", expectedHost,
+ System.getProperty("http.proxyHost"));
+ assertEquals("Incorrect proxy port system property.", expectedPort,
+ System.getProperty("http.proxyPort"));
+ }
+
+ private void assertNetworkHasExpectedProxy(ProxyInfo expected, Network network) {
+ LinkProperties lp = mCM.getLinkProperties(network);
+ assertNotNull("The network link properties object is null.", lp);
+ assertEquals("Incorrect proxy config.", expected, lp.getHttpProxy());
+
+ assertEquals(expected, mCM.getProxyForNetwork(network));
+ }
+
+ class ProxyChangeBroadcastReceiver extends BlockingBroadcastReceiver {
+ private boolean received;
+
+ public ProxyChangeBroadcastReceiver() {
+ super(VpnTest.this.getInstrumentation().getContext(), Proxy.PROXY_CHANGE_ACTION);
+ received = false;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!received) {
+ // Do not call onReceive() more than once.
+ super.onReceive(context, intent);
+ }
+ received = true;
+ }
+ }
}
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
index 2496c4a..ec536af 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
@@ -16,6 +16,7 @@
package com.android.cts.net.hostside.app2;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
+
import static com.android.cts.net.hostside.app2.Common.ACTION_RECEIVER_READY;
import static com.android.cts.net.hostside.app2.Common.DYNAMIC_RECEIVER;
import static com.android.cts.net.hostside.app2.Common.TAG;
@@ -26,13 +27,16 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
import android.os.IBinder;
-import android.os.Looper;
+import android.os.RemoteException;
import android.util.Log;
-import android.widget.Toast;
import com.android.cts.net.hostside.IMyService;
+import com.android.cts.net.hostside.INetworkCallback;
/**
* Service used to dynamically register a broadcast receiver.
@@ -40,7 +44,10 @@
public class MyService extends Service {
private static final String NOTIFICATION_CHANNEL_ID = "MyService";
+ ConnectivityManager mCm;
+
private MyBroadcastReceiver mReceiver;
+ private ConnectivityManager.NetworkCallback mNetworkCallback;
// TODO: move MyBroadcast static functions here - they were kept there to make git diff easier.
@@ -81,8 +88,67 @@
MyBroadcastReceiver .sendNotification(getApplicationContext(), NOTIFICATION_CHANNEL_ID,
notificationId, notificationType);
}
+
+ @Override
+ public void registerNetworkCallback(INetworkCallback cb) {
+ if (mNetworkCallback != null) {
+ Log.d(TAG, "unregister previous network callback: " + mNetworkCallback);
+ unregisterNetworkCallback();
+ }
+ Log.d(TAG, "registering network callback");
+
+ mNetworkCallback = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onBlockedStatusChanged(Network network, boolean blocked) {
+ try {
+ cb.onBlockedStatusChanged(network, blocked);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Cannot send onBlockedStatusChanged: " + e);
+ unregisterNetworkCallback();
+ }
+ }
+
+ @Override
+ public void onAvailable(Network network) {
+ try {
+ cb.onAvailable(network);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Cannot send onAvailable: " + e);
+ unregisterNetworkCallback();
+ }
+ }
+
+ @Override
+ public void onLost(Network network) {
+ try {
+ cb.onLost(network);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Cannot send onLost: " + e);
+ unregisterNetworkCallback();
+ }
+ }
+ };
+ mCm.registerNetworkCallback(makeWifiNetworkRequest(), mNetworkCallback);
+ try {
+ cb.asBinder().linkToDeath(() -> unregisterNetworkCallback(), 0);
+ } catch (RemoteException e) {
+ unregisterNetworkCallback();
+ }
+ }
};
+ private void unregisterNetworkCallback() {
+ Log.d(TAG, "unregistering network callback");
+ mCm.unregisterNetworkCallback(mNetworkCallback);
+ mNetworkCallback = null;
+ }
+
+ private NetworkRequest makeWifiNetworkRequest() {
+ return new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ }
+
@Override
public IBinder onBind(Intent intent) {
return mBinder;
@@ -94,6 +160,8 @@
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT));
+ mCm = (ConnectivityManager) getApplicationContext()
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
}
@Override
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
new file mode 100644
index 0000000..8d6c4ac
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.net;
+public class HostsideNetworkCallbackTests extends HostsideNetworkTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ uninstallPackage(TEST_APP2_PKG, false);
+ installPackage(TEST_APP2_APK);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ uninstallPackage(TEST_APP2_PKG, true);
+ }
+
+ public void testOnBlockedStatusChanged_data_saver() throws Exception {
+ runDeviceTests(TEST_PKG,
+ TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_data_saver");
+ }
+
+ public void testOnBlockedStatusChanged_power_saver() throws Exception {
+ runDeviceTests(TEST_PKG,
+ TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_power_saver");
+ }
+}
+
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
index fe9d36c..4598c39 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
@@ -81,6 +81,11 @@
"testGetRestrictBackgroundStatus_requiredWhitelistedPackages");
}
+ public void testDataSaverMode_broadcastNotSentOnUnsupportedDevices() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
+ "testBroadcastNotSentOnUnsupportedDevices");
+ }
+
/*****************************
* Battery Saver Mode tests. *
*****************************/
@@ -156,6 +161,11 @@
"testBackgroundNetworkAccess_enabled");
}
+ public void testAppIdleMetered_idleWhitelisted() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
+ "testAppIdleNetworkAccess_idleWhitelisted");
+ }
+
// TODO: currently power-save mode and idle uses the same whitelist, so this test would be
// redundant (as it would be testing the same as testBatterySaverMode_reinstall())
// public void testAppIdle_reinstall() throws Exception {
@@ -181,6 +191,11 @@
"testBackgroundNetworkAccess_enabled");
}
+ public void testAppIdleNonMetered_idleWhitelisted() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
+ "testAppIdleNetworkAccess_idleWhitelisted");
+ }
+
public void testAppIdleNonMetered_whenCharging() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testAppIdleNetworkAccess_whenCharging");
@@ -281,6 +296,21 @@
"testAppIdleAndBatterySaver_tempPowerSaveWhitelists");
}
+ public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+ "testDozeAndAppIdle_appIdleWhitelist");
+ }
+
+ public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+ "testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists");
+ }
+
+ public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
+ "testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists");
+ }
+
/*******************
* Helper methods. *
*******************/
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 69b07af..6e37a24 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
@@ -44,4 +44,51 @@
public void testAppDisallowed() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testAppDisallowed");
}
+
+ public void testGetConnectionOwnerUidSecurity() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testGetConnectionOwnerUidSecurity");
+ }
+
+ public void testSetProxy() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetProxy");
+ }
+
+ public void testSetProxyDisallowedApps() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetProxyDisallowedApps");
+ }
+
+ public void testNoProxy() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testNoProxy");
+ }
+
+ 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");
+ }
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/NetPolicyTestsPreparer.java b/tests/cts/hostside/src/com/android/cts/net/NetPolicyTestsPreparer.java
index ca14c27..bc2ee2c 100644
--- a/tests/cts/hostside/src/com/android/cts/net/NetPolicyTestsPreparer.java
+++ b/tests/cts/hostside/src/com/android/cts/net/NetPolicyTestsPreparer.java
@@ -48,7 +48,7 @@
}
private void setAppIdleConstants(String appIdleConstants) throws DeviceNotAvailableException {
- executeCmd("settings put global app_idle_constants " + appIdleConstants);
+ executeCmd("settings put global app_idle_constants \"" + appIdleConstants + "\"");
}
private String getAppIdleConstants() throws DeviceNotAvailableException {
diff --git a/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java b/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
new file mode 100644
index 0000000..19e61c6
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.IDeviceTest;
+
+import java.lang.Integer;
+import java.lang.String;
+import java.util.Arrays;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Host-side tests for values in /proc/net.
+ *
+ * These tests analyze /proc/net to verify that certain networking properties are correct.
+ */
+public class ProcNetTest extends DeviceTestCase implements IBuildReceiver, IDeviceTest {
+ private static final String SPI_TIMEOUT_SYSCTL = "/proc/sys/net/core/xfrm_acq_expires";
+ private static final int MIN_ACQ_EXPIRES = 3600;
+ // Global sysctls. Must be present and set to 1.
+ private static final String[] GLOBAL_SYSCTLS = {
+ "/proc/sys/net/ipv4/fwmark_reflect",
+ "/proc/sys/net/ipv6/fwmark_reflect",
+ "/proc/sys/net/ipv4/tcp_fwmark_accept",
+ };
+
+ // Per-interface IPv6 autoconf sysctls.
+ private static final String IPV6_SYSCTL_DIR = "/proc/sys/net/ipv6/conf";
+ private static final String AUTOCONF_SYSCTL = "accept_ra_rt_table";
+
+ // Expected values for MIN|MAX_PLEN.
+ private static final String ACCEPT_RA_RT_INFO_MIN_PLEN_STRING = "accept_ra_rt_info_min_plen";
+ private static final int ACCEPT_RA_RT_INFO_MIN_PLEN_VALUE = 48;
+ private static final String ACCEPT_RA_RT_INFO_MAX_PLEN_STRING = "accept_ra_rt_info_max_plen";
+ private static final int ACCEPT_RA_RT_INFO_MAX_PLEN_VALUE = 64;
+ // Expected values for RFC 7559 router soliciations.
+ // Maximum number of router solicitations to send. -1 means no limit.
+ private static final int IPV6_WIFI_ROUTER_SOLICITATIONS = -1;
+ private ITestDevice mDevice;
+ private IBuildInfo mBuild;
+ private String[] mSysctlDirs;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setBuild(IBuildInfo build) {
+ mBuild = build;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setDevice(ITestDevice device) {
+ super.setDevice(device);
+ mDevice = device;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mSysctlDirs = getSysctlDirs();
+ }
+
+ private String[] getSysctlDirs() throws Exception {
+ String interfaceDirs[] = mDevice.executeAdbCommand("shell", "ls", "-1",
+ IPV6_SYSCTL_DIR).split("\n");
+ List<String> interfaceDirsList = new ArrayList<String>(Arrays.asList(interfaceDirs));
+ interfaceDirsList.remove("all");
+ interfaceDirsList.remove("lo");
+ return interfaceDirsList.toArray(new String[interfaceDirsList.size()]);
+ }
+
+
+ protected void assertLess(String sysctl, int a, int b) {
+ assertTrue("value of " + sysctl + ": expected < " + b + " but was: " + a, a < b);
+ }
+
+ protected void assertAtLeast(String sysctl, int a, int b) {
+ assertTrue("value of " + sysctl + ": expected >= " + b + " but was: " + a, a >= b);
+ }
+
+ public int readIntFromPath(String path) throws Exception {
+ String mode = mDevice.executeAdbCommand("shell", "stat", "-c", "%a", path).trim();
+ String user = mDevice.executeAdbCommand("shell", "stat", "-c", "%u", path).trim();
+ String group = mDevice.executeAdbCommand("shell", "stat", "-c", "%g", path).trim();
+ assertEquals(mode, "644");
+ assertEquals(user, "0");
+ assertEquals(group, "0");
+ return Integer.parseInt(mDevice.executeAdbCommand("shell", "cat", path).trim());
+ }
+
+ /**
+ * Checks that SPI default timeouts are overridden, and set to a reasonable length of time
+ */
+ public void testMinAcqExpires() throws Exception {
+ int value = readIntFromPath(SPI_TIMEOUT_SYSCTL);
+ assertAtLeast(SPI_TIMEOUT_SYSCTL, value, MIN_ACQ_EXPIRES);
+ }
+
+ /**
+ * Checks that the sysctls for multinetwork kernel features are present and
+ * enabled.
+ */
+ public void testProcSysctls() throws Exception {
+ for (String sysctl : GLOBAL_SYSCTLS) {
+ int value = readIntFromPath(sysctl);
+ assertEquals(sysctl, 1, value);
+ }
+
+ for (String interfaceDir : mSysctlDirs) {
+ String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + AUTOCONF_SYSCTL;
+ int value = readIntFromPath(path);
+ assertLess(path, value, 0);
+ }
+ }
+
+ /**
+ * Verify that accept_ra_rt_info_{min,max}_plen exists and is set to the expected value
+ */
+ public void testAcceptRaRtInfoMinMaxPlen() throws Exception {
+ for (String interfaceDir : mSysctlDirs) {
+ String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "accept_ra_rt_info_min_plen";
+ int value = readIntFromPath(path);
+ assertEquals(path, value, ACCEPT_RA_RT_INFO_MIN_PLEN_VALUE);
+ path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "accept_ra_rt_info_max_plen";
+ value = readIntFromPath(path);
+ assertEquals(path, value, ACCEPT_RA_RT_INFO_MAX_PLEN_VALUE);
+ }
+ }
+
+ /**
+ * Verify that router_solicitations exists and is set to the expected value
+ * and verify that router_solicitation_max_interval exists and is in an acceptable interval.
+ */
+ public void testRouterSolicitations() throws Exception {
+ for (String interfaceDir : mSysctlDirs) {
+ String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "router_solicitations";
+ int value = readIntFromPath(path);
+ assertEquals(IPV6_WIFI_ROUTER_SOLICITATIONS, value);
+ path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "router_solicitation_max_interval";
+ int interval = readIntFromPath(path);
+ final int lowerBoundSec = 15 * 60;
+ final int upperBoundSec = 60 * 60;
+ assertTrue(lowerBoundSec <= interval);
+ assertTrue(interval <= upperBoundSec);
+ }
+ }
+}
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
new file mode 100644
index 0000000..b6ea4af
--- /dev/null
+++ b/tests/cts/net/Android.bp
@@ -0,0 +1,65 @@
+// Copyright (C) 2008 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+ name: "CtsNetTestCases",
+ defaults: ["cts_defaults"],
+
+ // Include both the 32 and 64 bit versions
+ compile_multilib: "both",
+
+ libs: [
+ "voip-common",
+ "org.apache.http.legacy",
+ "android.test.base.stubs",
+ ],
+
+ jni_libs: [
+ "libcts_jni",
+ "libnativedns_jni",
+ "libnativemultinetwork_jni",
+ "libnativehelper_compat_libc++",
+ ],
+
+ // include CtsTestServer as a temporary hack to free net.cts from cts.stub.
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "FrameworksNetCommonTests",
+ "core-tests-support",
+ "compatibility-device-util-axt",
+ "cts-net-utils",
+ "ctstestrunner-axt",
+ "ctstestserver",
+ "mockwebserver",
+ "junit",
+ "junit-params",
+ "truth-prebuilt",
+ ],
+
+ // uncomment when b/13249961 is fixed
+ // sdk_version: "current",
+ platform_apis: true,
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+
+}
diff --git a/tests/cts/net/Android.mk b/tests/cts/net/Android.mk
deleted file mode 100644
index 45941a7..0000000
--- a/tests/cts/net/Android.mk
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright (C) 2008 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# don't include this package in any target
-LOCAL_MODULE_TAGS := optional
-# and when built explicitly put it in the data partition
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-# Include both the 32 and 64 bit versions
-LOCAL_MULTILIB := both
-
-LOCAL_JAVA_LIBRARIES := \
- voip-common \
- conscrypt \
- org.apache.http.legacy \
- android.test.base.stubs \
-
-
-LOCAL_JNI_SHARED_LIBRARIES := libcts_jni libnativedns_jni \
- libnativemultinetwork_jni libnativehelper_compat_libc++
-
-# include CtsTestServer as a temporary hack to free net.cts from cts.stub.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsNetTestCases
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- core-tests-support \
- compatibility-device-util-axt \
- ctstestrunner-axt \
- ctstestserver \
- mockwebserver \
- junit \
- truth-prebuilt
-
-# uncomment when b/13249961 is fixed
-#LOCAL_SDK_VERSION := current
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-include $(BUILD_CTS_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/cts/net/AndroidManifest.xml b/tests/cts/net/AndroidManifest.xml
index b261b39..c2b3bf7 100644
--- a/tests/cts/net/AndroidManifest.xml
+++ b/tests/cts/net/AndroidManifest.xml
@@ -16,30 +16,28 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.net.cts">
+ package="android.net.cts"
+ android:targetSandboxVersion="2">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<application android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner" />
<uses-library android:name="org.apache.http.legacy" android:required="false" />
-
- <receiver android:name=".ConnectivityReceiver">
- <intent-filter>
- <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
- </intent-filter>
- </receiver>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/cts/net/AndroidTest.xml b/tests/cts/net/AndroidTest.xml
index 1326970..1807a6b 100644
--- a/tests/cts/net/AndroidTest.xml
+++ b/tests/cts/net/AndroidTest.xml
@@ -15,14 +15,18 @@
<configuration description="Config for CTS Net test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
+ <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsNetTestCases.apk" />
- <option name="test-file-name" value="CtsNetTestAppForApi23.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.net.cts" />
<option name="runtime-hint" value="9m4s" />
<option name="hidden-api-checks" value="false" />
+ <option name="isolated-storage" value="false" />
</test>
</configuration>
diff --git a/tests/cts/net/OWNERS b/tests/cts/net/OWNERS
index dc82bb0..d558556 100644
--- a/tests/cts/net/OWNERS
+++ b/tests/cts/net/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 31808
lorenzo@google.com
-satk@google.com
\ No newline at end of file
+satk@google.com
diff --git a/tests/cts/net/api23Test/Android.bp b/tests/cts/net/api23Test/Android.bp
new file mode 100644
index 0000000..ffe854e
--- /dev/null
+++ b/tests/cts/net/api23Test/Android.bp
@@ -0,0 +1,52 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+ name: "CtsNetApi23TestCases",
+ defaults: ["cts_defaults"],
+
+ // Include both the 32 and 64 bit versions
+ compile_multilib: "both",
+
+ libs: [
+ "android.test.base.stubs",
+ ],
+
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "core-tests-support",
+ "compatibility-device-util-axt",
+ "cts-net-utils",
+ "ctstestrunner-axt",
+ "ctstestserver",
+ "mockwebserver",
+ "junit",
+ "junit-params",
+ "truth-prebuilt",
+ ],
+
+ platform_apis: true,
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+
+}
diff --git a/tests/cts/net/api23Test/AndroidManifest.xml b/tests/cts/net/api23Test/AndroidManifest.xml
new file mode 100644
index 0000000..8af87f6
--- /dev/null
+++ b/tests/cts/net/api23Test/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.net.cts.api23test">
+
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <application android:usesCleartextTraffic="true">
+ <uses-library android:name="android.test.runner" />
+
+ <receiver android:name=".ConnectivityReceiver">
+ <intent-filter>
+ <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+ </intent-filter>
+ </receiver>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.net.cts.api23test"
+ android:label="CTS tests of android.net">
+ <meta-data android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+</manifest>
+
diff --git a/tests/cts/net/api23Test/AndroidTest.xml b/tests/cts/net/api23Test/AndroidTest.xml
new file mode 100644
index 0000000..21f28fc
--- /dev/null
+++ b/tests/cts/net/api23Test/AndroidTest.xml
@@ -0,0 +1,30 @@
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS Net API23 test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="networking" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="not-shardable" value="true" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsNetApi23TestCases.apk" />
+ <option name="test-file-name" value="CtsNetTestAppForApi23.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.net.cts.api23test" />
+ <option name="hidden-api-checks" value="false" />
+ </test>
+</configuration>
diff --git a/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java b/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java
new file mode 100644
index 0000000..cdb66e3
--- /dev/null
+++ b/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts.api23test;
+
+import static android.content.pm.PackageManager.FEATURE_WIFI;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.cts.util.CtsNetUtils;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class ConnectivityManagerApi23Test extends AndroidTestCase {
+ private static final String TAG = ConnectivityManagerApi23Test.class.getSimpleName();
+ private static final int SEND_BROADCAST_TIMEOUT = 30000;
+ // Intent string to get the number of wifi CONNECTIVITY_ACTION callbacks the test app has seen
+ public static final String GET_WIFI_CONNECTIVITY_ACTION_COUNT =
+ "android.net.cts.appForApi23.getWifiConnectivityActionCount";
+ // Action sent to ConnectivityActionReceiver when a network callback is sent via PendingIntent.
+
+ private Context mContext;
+ private PackageManager mPackageManager;
+ private CtsNetUtils mCtsNetUtils;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ Looper.prepare();
+ mContext = getContext();
+ mPackageManager = mContext.getPackageManager();
+ mCtsNetUtils = new CtsNetUtils(mContext);
+ }
+
+ /**
+ * Tests reporting of connectivity changed.
+ */
+ public void testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent() {
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+ Log.i(TAG, "testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent cannot execute unless device supports WiFi");
+ return;
+ }
+ ConnectivityReceiver.prepare();
+
+ mCtsNetUtils.toggleWifi();
+
+ // The connectivity broadcast has been sent; push through a terminal broadcast
+ // to wait for in the receive to confirm it didn't see the connectivity change.
+ Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
+ finalIntent.setClass(mContext, ConnectivityReceiver.class);
+ mContext.sendBroadcast(finalIntent);
+ assertFalse(ConnectivityReceiver.waitForBroadcast());
+ }
+
+ public void testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent()
+ throws InterruptedException {
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+ Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot"
+ + "execute unless device supports WiFi");
+ return;
+ }
+ mContext.startActivity(new Intent()
+ .setComponent(new ComponentName("android.net.cts.appForApi23",
+ "android.net.cts.appForApi23.ConnectivityListeningActivity"))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ Thread.sleep(200);
+
+ mCtsNetUtils.toggleWifi();
+
+ Intent getConnectivityCount = new Intent(GET_WIFI_CONNECTIVITY_ACTION_COUNT);
+ assertEquals(2, sendOrderedBroadcastAndReturnResultCode(
+ getConnectivityCount, SEND_BROADCAST_TIMEOUT));
+ }
+
+ public void testConnectivityChanged_whenRegistered_shouldReceiveIntent() {
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+ Log.i(TAG, "testConnectivityChanged_whenRegistered_shouldReceiveIntent cannot execute unless device supports WiFi");
+ return;
+ }
+ ConnectivityReceiver.prepare();
+ ConnectivityReceiver receiver = new ConnectivityReceiver();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ mContext.registerReceiver(receiver, filter);
+
+ mCtsNetUtils.toggleWifi();
+ Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
+ finalIntent.setClass(mContext, ConnectivityReceiver.class);
+ mContext.sendBroadcast(finalIntent);
+
+ assertTrue(ConnectivityReceiver.waitForBroadcast());
+ }
+
+ private int sendOrderedBroadcastAndReturnResultCode(
+ Intent intent, int timeoutMs) throws InterruptedException {
+ final LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<>(1);
+ mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ result.offer(getResultCode());
+ }
+ }, null, 0, null, null);
+
+ Integer resultCode = result.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ assertNotNull("Timed out (more than " + timeoutMs +
+ " milliseconds) waiting for result code for broadcast", resultCode);
+ return resultCode;
+ }
+
+}
\ No newline at end of file
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityReceiver.java b/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityReceiver.java
similarity index 98%
rename from tests/cts/net/src/android/net/cts/ConnectivityReceiver.java
rename to tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityReceiver.java
index 6a7b4a0..9d2b8ad 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityReceiver.java
+++ b/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityReceiver.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.net.cts;
+package android.net.cts.api23test;
import android.content.BroadcastReceiver;
import android.content.Context;
diff --git a/tests/cts/net/appForApi23/Android.bp b/tests/cts/net/appForApi23/Android.bp
new file mode 100644
index 0000000..82e2a08
--- /dev/null
+++ b/tests/cts/net/appForApi23/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+ name: "CtsNetTestAppForApi23",
+ defaults: ["cts_defaults"],
+
+ // Include both the 32 and 64 bit versions
+ compile_multilib: "both",
+
+ srcs: ["src/**/*.java"],
+
+ sdk_version: "23",
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+
+}
diff --git a/tests/cts/net/appForApi23/Android.mk b/tests/cts/net/appForApi23/Android.mk
deleted file mode 100644
index 54b60a0..0000000
--- a/tests/cts/net/appForApi23/Android.mk
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright (C) 2016 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# don't include this package in any target
-LOCAL_MODULE_TAGS := optional
-# and when built explicitly put it in the data partition
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-# Include both the 32 and 64 bit versions
-LOCAL_MULTILIB := both
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsNetTestAppForApi23
-
-LOCAL_SDK_VERSION := 23
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-include $(BUILD_CTS_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/cts/net/jni/Android.bp b/tests/cts/net/jni/Android.bp
new file mode 100644
index 0000000..baed48d
--- /dev/null
+++ b/tests/cts/net/jni/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library_shared {
+ name: "libnativedns_jni",
+
+ srcs: ["NativeDnsJni.c"],
+
+ shared_libs: [
+ "libnativehelper_compat_libc++",
+ "liblog",
+ ],
+ stl: "libc++_static",
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-unused-parameter",
+ ],
+
+}
+
+cc_library_shared {
+ name: "libnativemultinetwork_jni",
+
+ srcs: ["NativeMultinetworkJni.cpp"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-format",
+ ],
+ shared_libs: [
+ "libandroid",
+ "libnativehelper_compat_libc++",
+ "liblog",
+ ],
+ stl: "libc++_static",
+}
diff --git a/tests/cts/net/jni/Android.mk b/tests/cts/net/jni/Android.mk
deleted file mode 100644
index 727a44d..0000000
--- a/tests/cts/net/jni/Android.mk
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (C) 2013 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libnativedns_jni
-
-# Don't include this package in any configuration by default.
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := NativeDnsJni.c
-
-LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
-
-LOCAL_SHARED_LIBRARIES := libnativehelper_compat_libc++ liblog
-LOCAL_CXX_STL := libc++_static
-
-LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter
-
-include $(BUILD_SHARED_LIBRARY)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := libnativemultinetwork_jni
-# Don't include this package in any configuration by default.
-LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := NativeMultinetworkJni.c
-LOCAL_CFLAGS := -Wall -Werror -Wno-format
-LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
-LOCAL_SHARED_LIBRARIES := libandroid libnativehelper_compat_libc++ liblog
-LOCAL_CXX_STL := libc++_static
-include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/cts/net/jni/NativeMultinetworkJni.c b/tests/cts/net/jni/NativeMultinetworkJni.c
deleted file mode 100644
index 2fa5291..0000000
--- a/tests/cts/net/jni/NativeMultinetworkJni.c
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#define LOG_TAG "MultinetworkApiTest"
-#include <utils/Log.h>
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <inttypes.h>
-#include <jni.h>
-#include <netdb.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <android/multinetwork.h>
-
-#define UNUSED(X) ((void) (X))
-
-static const char kHostname[] = "connectivitycheck.android.com";
-
-
-JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runGetaddrinfoCheck(
- JNIEnv* env, jclass class, jlong nethandle) {
- UNUSED(env);
- UNUSED(class);
- net_handle_t handle = (net_handle_t) nethandle;
- struct addrinfo *res = NULL;
-
- errno = 0;
- int rval = android_getaddrinfofornetwork(handle, kHostname, NULL, NULL, &res);
- const int saved_errno = errno;
- freeaddrinfo(res);
-
- ALOGD("android_getaddrinfofornetwork(%" PRIu64 ", %s) returned rval=%d errno=%d",
- handle, kHostname, rval, saved_errno);
- return rval == 0 ? 0 : -saved_errno;
-}
-
-JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runSetprocnetwork(
- JNIEnv* env, jclass class, jlong nethandle) {
- UNUSED(env);
- UNUSED(class);
- net_handle_t handle = (net_handle_t) nethandle;
-
- errno = 0;
- int rval = android_setprocnetwork(handle);
- const int saved_errno = errno;
- ALOGD("android_setprocnetwork(%" PRIu64 ") returned rval=%d errno=%d",
- handle, rval, saved_errno);
- return rval == 0 ? 0 : -saved_errno;
-}
-
-JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runSetsocknetwork(
- JNIEnv* env, jclass class, jlong nethandle) {
- UNUSED(env);
- UNUSED(class);
- net_handle_t handle = (net_handle_t) nethandle;
-
- errno = 0;
- int fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
- if (fd < 0) {
- ALOGD("socket() failed, errno=%d", errno);
- return -errno;
- }
-
- errno = 0;
- int rval = android_setsocknetwork(handle, fd);
- const int saved_errno = errno;
- ALOGD("android_setprocnetwork(%" PRIu64 ", %d) returned rval=%d errno=%d",
- handle, fd, rval, saved_errno);
- close(fd);
- return rval == 0 ? 0 : -saved_errno;
-}
-
-// Use sizeof("x") - 1 because we need a compile-time constant, and strlen("x")
-// isn't guaranteed to fold to a constant.
-static const int kSockaddrStrLen = INET6_ADDRSTRLEN + sizeof("[]:65535") - 1;
-
-void sockaddr_ntop(const struct sockaddr *sa, socklen_t salen, char *dst, const size_t size) {
- char addrstr[INET6_ADDRSTRLEN];
- char portstr[sizeof("65535")];
- char buf[kSockaddrStrLen+1];
-
- int ret = getnameinfo(sa, salen,
- addrstr, sizeof(addrstr),
- portstr, sizeof(portstr),
- NI_NUMERICHOST | NI_NUMERICSERV);
- if (ret == 0) {
- snprintf(buf, sizeof(buf),
- (sa->sa_family == AF_INET6) ? "[%s]:%s" : "%s:%s",
- addrstr, portstr);
- } else {
- sprintf(buf, "???");
- }
-
- strlcpy(dst, buf, size);
-}
-
-JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runDatagramCheck(
- JNIEnv* env, jclass class, jlong nethandle) {
- UNUSED(env);
- UNUSED(class);
- const struct addrinfo kHints = {
- .ai_flags = AI_ADDRCONFIG,
- .ai_family = AF_UNSPEC,
- .ai_socktype = SOCK_DGRAM,
- .ai_protocol = IPPROTO_UDP,
- };
- struct addrinfo *res = NULL;
- net_handle_t handle = (net_handle_t) nethandle;
-
- static const char kPort[] = "443";
- int rval = android_getaddrinfofornetwork(handle, kHostname, kPort, &kHints, &res);
- if (rval != 0) {
- ALOGD("android_getaddrinfofornetwork(%llu, %s) returned rval=%d errno=%d",
- handle, kHostname, rval, errno);
- freeaddrinfo(res);
- return -errno;
- }
-
- // Rely upon getaddrinfo sorting the best destination to the front.
- int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
- if (fd < 0) {
- ALOGD("socket(%d, %d, %d) failed, errno=%d",
- res->ai_family, res->ai_socktype, res->ai_protocol, errno);
- freeaddrinfo(res);
- return -errno;
- }
-
- rval = android_setsocknetwork(handle, fd);
- ALOGD("android_setprocnetwork(%llu, %d) returned rval=%d errno=%d",
- handle, fd, rval, errno);
- if (rval != 0) {
- close(fd);
- freeaddrinfo(res);
- return -errno;
- }
-
- char addrstr[kSockaddrStrLen+1];
- sockaddr_ntop(res->ai_addr, res->ai_addrlen, addrstr, sizeof(addrstr));
- ALOGD("Attempting connect() to %s ...", addrstr);
-
- rval = connect(fd, res->ai_addr, res->ai_addrlen);
- if (rval != 0) {
- close(fd);
- freeaddrinfo(res);
- return -errno;
- }
- freeaddrinfo(res);
-
- struct sockaddr_storage src_addr;
- socklen_t src_addrlen = sizeof(src_addr);
- if (getsockname(fd, (struct sockaddr *)&src_addr, &src_addrlen) != 0) {
- close(fd);
- return -errno;
- }
- sockaddr_ntop((const struct sockaddr *)&src_addr, sizeof(src_addr), addrstr, sizeof(addrstr));
- ALOGD("... from %s", addrstr);
-
- // Don't let reads or writes block indefinitely.
- const struct timeval timeo = { 2, 0 }; // 2 seconds
- setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
- setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo));
-
- // For reference see:
- // https://tools.ietf.org/html/draft-tsvwg-quic-protocol-01#section-6.1
- uint8_t quic_packet[] = {
- 0x0c, // public flags: 64bit conn ID, 8bit sequence number
- 0, 0, 0, 0, 0, 0, 0, 0, // 64bit connection ID
- 0x01, // sequence number
- 0x00, // private flags
- 0x07, // type: regular frame type "PING"
- };
-
- arc4random_buf(quic_packet + 1, 8); // random connection ID
-
- uint8_t response[1500];
- ssize_t sent, rcvd;
- static const int MAX_RETRIES = 5;
- int i, errnum = 0;
-
- for (i = 0; i < MAX_RETRIES; i++) {
- sent = send(fd, quic_packet, sizeof(quic_packet), 0);
- if (sent < (ssize_t)sizeof(quic_packet)) {
- errnum = errno;
- ALOGD("send(QUIC packet) returned sent=%zd, errno=%d", sent, errnum);
- close(fd);
- return -errnum;
- }
-
- rcvd = recv(fd, response, sizeof(response), 0);
- if (rcvd > 0) {
- break;
- } else {
- errnum = errno;
- ALOGD("[%d/%d] recv(QUIC response) returned rcvd=%zd, errno=%d",
- i + 1, MAX_RETRIES, rcvd, errnum);
- }
- }
- if (rcvd < sent) {
- ALOGD("QUIC UDP %s: sent=%zd but rcvd=%zd, errno=%d", kPort, sent, rcvd, errnum);
- if (rcvd <= 0) {
- ALOGD("Does this network block UDP port %s?", kPort);
- }
- close(fd);
- return -EPROTO;
- }
-
- int conn_id_cmp = memcmp(quic_packet + 1, response + 1, 8);
- if (conn_id_cmp != 0) {
- ALOGD("sent and received connection IDs do not match");
- close(fd);
- return -EPROTO;
- }
-
- // TODO: log, and compare to the IP address encoded in the
- // response, since this should be a public reset packet.
-
- close(fd);
- return 0;
-}
diff --git a/tests/cts/net/jni/NativeMultinetworkJni.cpp b/tests/cts/net/jni/NativeMultinetworkJni.cpp
new file mode 100644
index 0000000..a6b5e90
--- /dev/null
+++ b/tests/cts/net/jni/NativeMultinetworkJni.cpp
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#define LOG_TAG "MultinetworkApiTest"
+#include <utils/Log.h>
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <jni.h>
+#include <netdb.h>
+#include <poll.h> /* poll */
+#include <resolv.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <string>
+
+#include <android/multinetwork.h>
+#include <nativehelper/JNIHelp.h>
+
+#define EXPECT_GE(env, actual, expected, msg) \
+ do { \
+ if (actual < expected) { \
+ jniThrowExceptionFmt(env, "java/lang/AssertionError", \
+ "%s:%d: %s EXPECT_GE: expected %d, got %d", \
+ __FILE__, __LINE__, msg, expected, actual); \
+ } \
+ } while (0)
+
+#define EXPECT_GT(env, actual, expected, msg) \
+ do { \
+ if (actual <= expected) { \
+ jniThrowExceptionFmt(env, "java/lang/AssertionError", \
+ "%s:%d: %s EXPECT_GT: expected %d, got %d", \
+ __FILE__, __LINE__, msg, expected, actual); \
+ } \
+ } while (0)
+
+#define EXPECT_EQ(env, expected, actual, msg) \
+ do { \
+ if (actual != expected) { \
+ jniThrowExceptionFmt(env, "java/lang/AssertionError", \
+ "%s:%d: %s EXPECT_EQ: expected %d, got %d", \
+ __FILE__, __LINE__, msg, expected, actual); \
+ } \
+ } while (0)
+
+static const int MAXPACKET = 8 * 1024;
+static const int TIMEOUT_MS = 15000;
+static const char kHostname[] = "connectivitycheck.android.com";
+static const char kNxDomainName[] = "test1-nx.metric.gstatic.com";
+static const char kGoogleName[] = "www.google.com";
+
+int makeQuery(const char* name, int qtype, uint8_t* buf, size_t buflen) {
+ return res_mkquery(ns_o_query, name, ns_c_in, qtype, NULL, 0, NULL, buf, buflen);
+}
+
+int getAsyncResponse(JNIEnv* env, int fd, int timeoutMs, int* rcode, uint8_t* buf, size_t bufLen) {
+ struct pollfd wait_fd = { .fd = fd, .events = POLLIN };
+
+ poll(&wait_fd, 1, timeoutMs);
+ if (wait_fd.revents & POLLIN) {
+ int n = android_res_nresult(fd, rcode, buf, bufLen);
+ // Verify that android_res_nresult() closed the fd
+ char dummy;
+ EXPECT_EQ(env, -1, read(fd, &dummy, sizeof(dummy)), "res_nresult check for closing fd");
+ EXPECT_EQ(env, EBADF, errno, "res_nresult check for errno");
+ return n;
+ }
+
+ return -ETIMEDOUT;
+}
+
+int extractIpAddressAnswers(uint8_t* buf, size_t bufLen, int family) {
+ ns_msg handle;
+ if (ns_initparse((const uint8_t*) buf, bufLen, &handle) < 0) {
+ return -errno;
+ }
+ const int ancount = ns_msg_count(handle, ns_s_an);
+ // Answer count = 0 is valid(e.g. response of query with root)
+ if (!ancount) {
+ return 0;
+ }
+ ns_rr rr;
+ bool hasValidAns = false;
+ for (int i = 0; i < ancount; i++) {
+ if (ns_parserr(&handle, ns_s_an, i, &rr) < 0) {
+ // If there is no valid answer, test will fail.
+ continue;
+ }
+ const uint8_t* rdata = ns_rr_rdata(rr);
+ char buffer[INET6_ADDRSTRLEN];
+ if (inet_ntop(family, (const char*) rdata, buffer, sizeof(buffer)) == NULL) {
+ return -errno;
+ }
+ hasValidAns = true;
+ }
+ return hasValidAns ? 0 : -EBADMSG;
+}
+
+int expectAnswersValid(JNIEnv* env, int fd, int family, int expectedRcode) {
+ int rcode = -1;
+ uint8_t buf[MAXPACKET] = {};
+ int res = getAsyncResponse(env, fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
+ if (res < 0) {
+ return res;
+ }
+
+ EXPECT_EQ(env, expectedRcode, rcode, "rcode is not expected");
+
+ if (expectedRcode == ns_r_noerror && res > 0) {
+ return extractIpAddressAnswers(buf, res, family);
+ }
+ return 0;
+}
+
+int expectAnswersNotValid(JNIEnv* env, int fd, int expectedErrno) {
+ int rcode = -1;
+ uint8_t buf[MAXPACKET] = {};
+ int res = getAsyncResponse(env, fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
+ if (res != expectedErrno) {
+ ALOGD("res:%d, expectedErrno = %d", res, expectedErrno);
+ return (res > 0) ? -EREMOTEIO : res;
+ }
+ return 0;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runResNqueryCheck(
+ JNIEnv* env, jclass, jlong nethandle) {
+ net_handle_t handle = (net_handle_t) nethandle;
+
+ // V4
+ int fd = android_res_nquery(handle, kHostname, ns_c_in, ns_t_a, 0);
+ EXPECT_GE(env, fd, 0, "v4 res_nquery");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
+ "v4 res_nquery check answers");
+
+ // V4 NXDOMAIN
+ fd = android_res_nquery(handle, kNxDomainName, ns_c_in, ns_t_a, 0);
+ EXPECT_GE(env, fd, 0, "v4 res_nquery NXDOMAIN");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_nxdomain),
+ "v4 res_nquery NXDOMAIN check answers");
+
+ // V6
+ fd = android_res_nquery(handle, kHostname, ns_c_in, ns_t_aaaa, 0);
+ EXPECT_GE(env, fd, 0, "v6 res_nquery");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
+ "v6 res_nquery check answers");
+
+ // V6 NXDOMAIN
+ fd = android_res_nquery(handle, kNxDomainName, ns_c_in, ns_t_aaaa, 0);
+ EXPECT_GE(env, fd, 0, "v6 res_nquery NXDOMAIN");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_nxdomain),
+ "v6 res_nquery NXDOMAIN check answers");
+
+ return 0;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runResNsendCheck(
+ JNIEnv* env, jclass, jlong nethandle) {
+ net_handle_t handle = (net_handle_t) nethandle;
+ // V4
+ uint8_t buf1[MAXPACKET] = {};
+
+ int len1 = makeQuery(kGoogleName, ns_t_a, buf1, sizeof(buf1));
+ EXPECT_GT(env, len1, 0, "v4 res_mkquery 1st");
+
+ uint8_t buf2[MAXPACKET] = {};
+ int len2 = makeQuery(kHostname, ns_t_a, buf2, sizeof(buf2));
+ EXPECT_GT(env, len2, 0, "v4 res_mkquery 2nd");
+
+ int fd1 = android_res_nsend(handle, buf1, len1, 0);
+ EXPECT_GE(env, fd1, 0, "v4 res_nsend 1st");
+ int fd2 = android_res_nsend(handle, buf2, len2, 0);
+ EXPECT_GE(env, fd2, 0, "v4 res_nsend 2nd");
+
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd2, AF_INET, ns_r_noerror),
+ "v4 res_nsend 2nd check answers");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET, ns_r_noerror),
+ "v4 res_nsend 1st check answers");
+
+ // V4 NXDOMAIN
+ memset(buf1, 0, sizeof(buf1));
+ len1 = makeQuery(kNxDomainName, ns_t_a, buf1, sizeof(buf1));
+ EXPECT_GT(env, len1, 0, "v4 res_mkquery NXDOMAIN");
+ fd1 = android_res_nsend(handle, buf1, len1, 0);
+ EXPECT_GE(env, fd1, 0, "v4 res_nsend NXDOMAIN");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET, ns_r_nxdomain),
+ "v4 res_nsend NXDOMAIN check answers");
+
+ // V6
+ memset(buf1, 0, sizeof(buf1));
+ memset(buf2, 0, sizeof(buf2));
+ len1 = makeQuery(kGoogleName, ns_t_aaaa, buf1, sizeof(buf1));
+ EXPECT_GT(env, len1, 0, "v6 res_mkquery 1st");
+ len2 = makeQuery(kHostname, ns_t_aaaa, buf2, sizeof(buf2));
+ EXPECT_GT(env, len2, 0, "v6 res_mkquery 2nd");
+
+ fd1 = android_res_nsend(handle, buf1, len1, 0);
+ EXPECT_GE(env, fd1, 0, "v6 res_nsend 1st");
+ fd2 = android_res_nsend(handle, buf2, len2, 0);
+ EXPECT_GE(env, fd2, 0, "v6 res_nsend 2nd");
+
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd2, AF_INET6, ns_r_noerror),
+ "v6 res_nsend 2nd check answers");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET6, ns_r_noerror),
+ "v6 res_nsend 1st check answers");
+
+ // V6 NXDOMAIN
+ memset(buf1, 0, sizeof(buf1));
+ len1 = makeQuery(kNxDomainName, ns_t_aaaa, buf1, sizeof(buf1));
+ EXPECT_GT(env, len1, 0, "v6 res_mkquery NXDOMAIN");
+ fd1 = android_res_nsend(handle, buf1, len1, 0);
+ EXPECT_GE(env, fd1, 0, "v6 res_nsend NXDOMAIN");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET6, ns_r_nxdomain),
+ "v6 res_nsend NXDOMAIN check answers");
+
+ return 0;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runResNcancelCheck(
+ JNIEnv* env, jclass, jlong nethandle) {
+ net_handle_t handle = (net_handle_t) nethandle;
+
+ int fd = android_res_nquery(handle, kGoogleName, ns_c_in, ns_t_a, 0);
+ errno = 0;
+ android_res_cancel(fd);
+ int err = errno;
+ EXPECT_EQ(env, 0, err, "res_cancel");
+ // DO NOT call cancel or result with the same fd more than once,
+ // otherwise it will hit fdsan double-close fd.
+ return 0;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runResNapiMalformedCheck(
+ JNIEnv* env, jclass, jlong nethandle) {
+ net_handle_t handle = (net_handle_t) nethandle;
+
+ // It is the equivalent of "dig . a", Query with an empty name.
+ int fd = android_res_nquery(handle, "", ns_c_in, ns_t_a, 0);
+ EXPECT_GE(env, fd, 0, "res_nquery root");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
+ "res_nquery root check answers");
+
+ // Label limit 63
+ std::string exceedingLabelQuery = "www." + std::string(70, 'g') + ".com";
+ // Name limit 255
+ std::string exceedingDomainQuery = "www." + std::string(255, 'g') + ".com";
+
+ fd = android_res_nquery(handle, exceedingLabelQuery.c_str(), ns_c_in, ns_t_a, 0);
+ EXPECT_EQ(env, -EMSGSIZE, fd, "res_nquery exceedingLabelQuery");
+ fd = android_res_nquery(handle, exceedingDomainQuery.c_str(), ns_c_in, ns_t_aaaa, 0);
+ EXPECT_EQ(env, -EMSGSIZE, fd, "res_nquery exceedingDomainQuery");
+
+ uint8_t buf[10] = {};
+ // empty BLOB
+ fd = android_res_nsend(handle, buf, 10, 0);
+ EXPECT_GE(env, fd, 0, "res_nsend empty BLOB");
+ EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
+ "res_nsend empty BLOB check answers");
+
+ uint8_t largeBuf[2 * MAXPACKET] = {};
+ // A buffer larger than 8KB
+ fd = android_res_nsend(handle, largeBuf, sizeof(largeBuf), 0);
+ EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend buffer larger than 8KB");
+
+ // 5000 bytes filled with 0. This returns EMSGSIZE because FrameworkListener limits the size of
+ // commands to 4096 bytes.
+ fd = android_res_nsend(handle, largeBuf, 5000, 0);
+ EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend 5000 bytes filled with 0");
+
+ // 500 bytes filled with 0
+ fd = android_res_nsend(handle, largeBuf, 500, 0);
+ EXPECT_GE(env, fd, 0, "res_nsend 500 bytes filled with 0");
+ EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
+ "res_nsend 500 bytes filled with 0 check answers");
+
+ // 5000 bytes filled with 0xFF
+ uint8_t ffBuf[5001] = {};
+ memset(ffBuf, 0xFF, sizeof(ffBuf));
+ ffBuf[5000] = '\0';
+ fd = android_res_nsend(handle, ffBuf, sizeof(ffBuf), 0);
+ EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend 5000 bytes filled with 0xFF");
+
+ // 500 bytes filled with 0xFF
+ ffBuf[500] = '\0';
+ fd = android_res_nsend(handle, ffBuf, 501, 0);
+ EXPECT_GE(env, fd, 0, "res_nsend 500 bytes filled with 0xFF");
+ EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
+ "res_nsend 500 bytes filled with 0xFF check answers");
+
+ return 0;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runGetaddrinfoCheck(
+ JNIEnv*, jclass, jlong nethandle) {
+ net_handle_t handle = (net_handle_t) nethandle;
+ struct addrinfo *res = NULL;
+
+ errno = 0;
+ int rval = android_getaddrinfofornetwork(handle, kHostname, NULL, NULL, &res);
+ const int saved_errno = errno;
+ freeaddrinfo(res);
+
+ ALOGD("android_getaddrinfofornetwork(%" PRIu64 ", %s) returned rval=%d errno=%d",
+ handle, kHostname, rval, saved_errno);
+ return rval == 0 ? 0 : -saved_errno;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runSetprocnetwork(
+ JNIEnv*, jclass, jlong nethandle) {
+ net_handle_t handle = (net_handle_t) nethandle;
+
+ errno = 0;
+ int rval = android_setprocnetwork(handle);
+ const int saved_errno = errno;
+ ALOGD("android_setprocnetwork(%" PRIu64 ") returned rval=%d errno=%d",
+ handle, rval, saved_errno);
+ return rval == 0 ? 0 : -saved_errno;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runSetsocknetwork(
+ JNIEnv*, jclass, jlong nethandle) {
+ net_handle_t handle = (net_handle_t) nethandle;
+
+ errno = 0;
+ int fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd < 0) {
+ ALOGD("socket() failed, errno=%d", errno);
+ return -errno;
+ }
+
+ errno = 0;
+ int rval = android_setsocknetwork(handle, fd);
+ const int saved_errno = errno;
+ ALOGD("android_setprocnetwork(%" PRIu64 ", %d) returned rval=%d errno=%d",
+ handle, fd, rval, saved_errno);
+ close(fd);
+ return rval == 0 ? 0 : -saved_errno;
+}
+
+// Use sizeof("x") - 1 because we need a compile-time constant, and strlen("x")
+// isn't guaranteed to fold to a constant.
+static const int kSockaddrStrLen = INET6_ADDRSTRLEN + sizeof("[]:65535") - 1;
+
+void sockaddr_ntop(const struct sockaddr *sa, socklen_t salen, char *dst, const size_t size) {
+ char addrstr[INET6_ADDRSTRLEN];
+ char portstr[sizeof("65535")];
+ char buf[kSockaddrStrLen+1];
+
+ int ret = getnameinfo(sa, salen,
+ addrstr, sizeof(addrstr),
+ portstr, sizeof(portstr),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (ret == 0) {
+ snprintf(buf, sizeof(buf),
+ (sa->sa_family == AF_INET6) ? "[%s]:%s" : "%s:%s",
+ addrstr, portstr);
+ } else {
+ sprintf(buf, "???");
+ }
+
+ strlcpy(dst, buf, size);
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runDatagramCheck(
+ JNIEnv*, jclass, jlong nethandle) {
+ const struct addrinfo kHints = {
+ .ai_flags = AI_ADDRCONFIG,
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_DGRAM,
+ .ai_protocol = IPPROTO_UDP,
+ };
+ struct addrinfo *res = NULL;
+ net_handle_t handle = (net_handle_t) nethandle;
+
+ static const char kPort[] = "443";
+ int rval = android_getaddrinfofornetwork(handle, kHostname, kPort, &kHints, &res);
+ if (rval != 0) {
+ ALOGD("android_getaddrinfofornetwork(%llu, %s) returned rval=%d errno=%d",
+ handle, kHostname, rval, errno);
+ freeaddrinfo(res);
+ return -errno;
+ }
+
+ // Rely upon getaddrinfo sorting the best destination to the front.
+ int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (fd < 0) {
+ ALOGD("socket(%d, %d, %d) failed, errno=%d",
+ res->ai_family, res->ai_socktype, res->ai_protocol, errno);
+ freeaddrinfo(res);
+ return -errno;
+ }
+
+ rval = android_setsocknetwork(handle, fd);
+ ALOGD("android_setprocnetwork(%llu, %d) returned rval=%d errno=%d",
+ handle, fd, rval, errno);
+ if (rval != 0) {
+ close(fd);
+ freeaddrinfo(res);
+ return -errno;
+ }
+
+ char addrstr[kSockaddrStrLen+1];
+ sockaddr_ntop(res->ai_addr, res->ai_addrlen, addrstr, sizeof(addrstr));
+ ALOGD("Attempting connect() to %s ...", addrstr);
+
+ rval = connect(fd, res->ai_addr, res->ai_addrlen);
+ if (rval != 0) {
+ close(fd);
+ freeaddrinfo(res);
+ return -errno;
+ }
+ freeaddrinfo(res);
+
+ struct sockaddr_storage src_addr;
+ socklen_t src_addrlen = sizeof(src_addr);
+ if (getsockname(fd, (struct sockaddr *)&src_addr, &src_addrlen) != 0) {
+ close(fd);
+ return -errno;
+ }
+ sockaddr_ntop((const struct sockaddr *)&src_addr, sizeof(src_addr), addrstr, sizeof(addrstr));
+ ALOGD("... from %s", addrstr);
+
+ // Don't let reads or writes block indefinitely.
+ const struct timeval timeo = { 2, 0 }; // 2 seconds
+ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo));
+
+ // For reference see:
+ // https://tools.ietf.org/html/draft-tsvwg-quic-protocol-01#section-6.1
+ uint8_t quic_packet[] = {
+ 0x0c, // public flags: 64bit conn ID, 8bit sequence number
+ 0, 0, 0, 0, 0, 0, 0, 0, // 64bit connection ID
+ 0x01, // sequence number
+ 0x00, // private flags
+ 0x07, // type: regular frame type "PING"
+ };
+
+ arc4random_buf(quic_packet + 1, 8); // random connection ID
+
+ uint8_t response[1500];
+ ssize_t sent, rcvd;
+ static const int MAX_RETRIES = 5;
+ int i, errnum = 0;
+
+ for (i = 0; i < MAX_RETRIES; i++) {
+ sent = send(fd, quic_packet, sizeof(quic_packet), 0);
+ if (sent < (ssize_t)sizeof(quic_packet)) {
+ errnum = errno;
+ ALOGD("send(QUIC packet) returned sent=%zd, errno=%d", sent, errnum);
+ close(fd);
+ return -errnum;
+ }
+
+ rcvd = recv(fd, response, sizeof(response), 0);
+ if (rcvd > 0) {
+ break;
+ } else {
+ errnum = errno;
+ ALOGD("[%d/%d] recv(QUIC response) returned rcvd=%zd, errno=%d",
+ i + 1, MAX_RETRIES, rcvd, errnum);
+ }
+ }
+ if (rcvd < sent) {
+ ALOGD("QUIC UDP %s: sent=%zd but rcvd=%zd, errno=%d", kPort, sent, rcvd, errnum);
+ if (rcvd <= 0) {
+ ALOGD("Does this network block UDP port %s?", kPort);
+ }
+ close(fd);
+ return -EPROTO;
+ }
+
+ int conn_id_cmp = memcmp(quic_packet + 1, response + 1, 8);
+ if (conn_id_cmp != 0) {
+ ALOGD("sent and received connection IDs do not match");
+ close(fd);
+ return -EPROTO;
+ }
+
+ // TODO: log, and compare to the IP address encoded in the
+ // response, since this should be a public reset packet.
+
+ close(fd);
+ return 0;
+}
diff --git a/tests/cts/net/native/Android.mk b/tests/cts/net/native/Android.mk
deleted file mode 100644
index b798d87..0000000
--- a/tests/cts/net/native/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-include $(call all-subdir-makefiles)
diff --git a/tests/cts/net/native/dns/Android.bp b/tests/cts/net/native/dns/Android.bp
new file mode 100644
index 0000000..9fbc3fc
--- /dev/null
+++ b/tests/cts/net/native/dns/Android.bp
@@ -0,0 +1,39 @@
+cc_defaults {
+ name: "dns_async_defaults",
+
+ cflags: [
+ "-fstack-protector-all",
+ "-g",
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ "-Wnullable-to-nonnull-conversion",
+ "-Wsign-compare",
+ "-Wthread-safety",
+ "-Wunused-parameter",
+ ],
+ srcs: [
+ "NativeDnsAsyncTest.cpp",
+ ],
+ shared_libs: [
+ "libandroid",
+ "liblog",
+ "libutils",
+ ],
+}
+
+cc_test {
+ name: "CtsNativeNetDnsTestCases",
+ defaults: ["dns_async_defaults"],
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+ test_suites: [
+ "cts",
+ ],
+}
\ No newline at end of file
diff --git a/tests/cts/net/native/dns/AndroidTest.xml b/tests/cts/net/native/dns/AndroidTest.xml
new file mode 100644
index 0000000..fe88cda
--- /dev/null
+++ b/tests/cts/net/native/dns/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS Native Network dns test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="networking" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsNativeNetDnsTestCases->/data/local/tmp/CtsNativeNetDnsTestCases" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="CtsNativeNetDnsTestCases" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+</configuration>
diff --git a/tests/cts/net/native/dns/NativeDnsAsyncTest.cpp b/tests/cts/net/native/dns/NativeDnsAsyncTest.cpp
new file mode 100644
index 0000000..e501475
--- /dev/null
+++ b/tests/cts/net/native/dns/NativeDnsAsyncTest.cpp
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <error.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <poll.h> /* poll */
+#include <resolv.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include <android/multinetwork.h>
+#include <gtest/gtest.h>
+
+namespace {
+constexpr int MAXPACKET = 8 * 1024;
+constexpr int PTON_MAX = 16;
+constexpr int TIMEOUT_MS = 10000;
+
+int getAsyncResponse(int fd, int timeoutMs, int* rcode, uint8_t* buf, size_t bufLen) {
+ struct pollfd wait_fd[1];
+ wait_fd[0].fd = fd;
+ wait_fd[0].events = POLLIN;
+ short revents;
+ int ret;
+ ret = poll(wait_fd, 1, timeoutMs);
+ revents = wait_fd[0].revents;
+ if (revents & POLLIN) {
+ int n = android_res_nresult(fd, rcode, buf, bufLen);
+ // Verify that android_res_nresult() closed the fd
+ char dummy;
+ EXPECT_EQ(-1, read(fd, &dummy, sizeof dummy));
+ EXPECT_EQ(EBADF, errno);
+ return n;
+ }
+
+ return -1;
+}
+
+std::vector<std::string> extractIpAddressAnswers(uint8_t* buf, size_t bufLen, int ipType) {
+ ns_msg handle;
+ if (ns_initparse((const uint8_t*) buf, bufLen, &handle) < 0) {
+ return {};
+ }
+ const int ancount = ns_msg_count(handle, ns_s_an);
+ ns_rr rr;
+ std::vector<std::string> answers;
+ for (int i = 0; i < ancount; i++) {
+ if (ns_parserr(&handle, ns_s_an, i, &rr) < 0) {
+ continue;
+ }
+ const uint8_t* rdata = ns_rr_rdata(rr);
+ char buffer[INET6_ADDRSTRLEN];
+ if (inet_ntop(ipType, (const char*) rdata, buffer, sizeof(buffer))) {
+ answers.push_back(buffer);
+ }
+ }
+ return answers;
+}
+
+void expectAnswersValid(int fd, int ipType, int expectedRcode) {
+ int rcode = -1;
+ uint8_t buf[MAXPACKET] = {};
+ int res = getAsyncResponse(fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
+ EXPECT_GE(res, 0);
+ EXPECT_EQ(rcode, expectedRcode);
+
+ if (expectedRcode == ns_r_noerror) {
+ auto answers = extractIpAddressAnswers(buf, res, ipType);
+ EXPECT_GE(answers.size(), 0U);
+ for (auto &answer : answers) {
+ char pton[PTON_MAX];
+ EXPECT_EQ(1, inet_pton(ipType, answer.c_str(), pton));
+ }
+ }
+}
+
+void expectAnswersNotValid(int fd, int expectedErrno) {
+ int rcode = -1;
+ uint8_t buf[MAXPACKET] = {};
+ int res = getAsyncResponse(fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
+ EXPECT_EQ(expectedErrno, res);
+}
+
+} // namespace
+
+TEST (NativeDnsAsyncTest, Async_Query) {
+ // V4
+ int fd1 = android_res_nquery(
+ NETWORK_UNSPECIFIED, "www.google.com", ns_c_in, ns_t_a, 0);
+ EXPECT_GE(fd1, 0);
+ int fd2 = android_res_nquery(
+ NETWORK_UNSPECIFIED, "www.youtube.com", ns_c_in, ns_t_a, 0);
+ EXPECT_GE(fd2, 0);
+ expectAnswersValid(fd2, AF_INET, ns_r_noerror);
+ expectAnswersValid(fd1, AF_INET, ns_r_noerror);
+
+ // V6
+ fd1 = android_res_nquery(
+ NETWORK_UNSPECIFIED, "www.google.com", ns_c_in, ns_t_aaaa, 0);
+ EXPECT_GE(fd1, 0);
+ fd2 = android_res_nquery(
+ NETWORK_UNSPECIFIED, "www.youtube.com", ns_c_in, ns_t_aaaa, 0);
+ EXPECT_GE(fd2, 0);
+ expectAnswersValid(fd2, AF_INET6, ns_r_noerror);
+ expectAnswersValid(fd1, AF_INET6, ns_r_noerror);
+}
+
+TEST (NativeDnsAsyncTest, Async_Send) {
+ // V4
+ uint8_t buf1[MAXPACKET] = {};
+ int len1 = res_mkquery(ns_o_query, "www.googleapis.com",
+ ns_c_in, ns_t_a, nullptr, 0, nullptr, buf1, sizeof(buf1));
+ EXPECT_GT(len1, 0);
+
+ uint8_t buf2[MAXPACKET] = {};
+ int len2 = res_mkquery(ns_o_query, "play.googleapis.com",
+ ns_c_in, ns_t_a, nullptr, 0, nullptr, buf2, sizeof(buf2));
+ EXPECT_GT(len2, 0);
+
+ int fd1 = android_res_nsend(NETWORK_UNSPECIFIED, buf1, len1, 0);
+ EXPECT_GE(fd1, 0);
+ int fd2 = android_res_nsend(NETWORK_UNSPECIFIED, buf2, len2, 0);
+ EXPECT_GE(fd2, 0);
+
+ expectAnswersValid(fd2, AF_INET, ns_r_noerror);
+ expectAnswersValid(fd1, AF_INET, ns_r_noerror);
+
+ // V6
+ memset(buf1, 0, sizeof(buf1));
+ memset(buf2, 0, sizeof(buf2));
+ len1 = res_mkquery(ns_o_query, "www.googleapis.com",
+ ns_c_in, ns_t_aaaa, nullptr, 0, nullptr, buf1, sizeof(buf1));
+ EXPECT_GT(len1, 0);
+ len2 = res_mkquery(ns_o_query, "play.googleapis.com",
+ ns_c_in, ns_t_aaaa, nullptr, 0, nullptr, buf2, sizeof(buf2));
+ EXPECT_GT(len2, 0);
+
+ fd1 = android_res_nsend(NETWORK_UNSPECIFIED, buf1, len1, 0);
+ EXPECT_GE(fd1, 0);
+ fd2 = android_res_nsend(NETWORK_UNSPECIFIED, buf2, len2, 0);
+ EXPECT_GE(fd2, 0);
+
+ expectAnswersValid(fd2, AF_INET6, ns_r_noerror);
+ expectAnswersValid(fd1, AF_INET6, ns_r_noerror);
+}
+
+TEST (NativeDnsAsyncTest, Async_NXDOMAIN) {
+ uint8_t buf[MAXPACKET] = {};
+ int len = res_mkquery(ns_o_query, "test1-nx.metric.gstatic.com",
+ ns_c_in, ns_t_a, nullptr, 0, nullptr, buf, sizeof(buf));
+ EXPECT_GT(len, 0);
+ int fd1 = android_res_nsend(NETWORK_UNSPECIFIED, buf, len, ANDROID_RESOLV_NO_CACHE_LOOKUP);
+ EXPECT_GE(fd1, 0);
+
+ len = res_mkquery(ns_o_query, "test2-nx.metric.gstatic.com",
+ ns_c_in, ns_t_a, nullptr, 0, nullptr, buf, sizeof(buf));
+ EXPECT_GT(len, 0);
+ int fd2 = android_res_nsend(NETWORK_UNSPECIFIED, buf, len, ANDROID_RESOLV_NO_CACHE_LOOKUP);
+ EXPECT_GE(fd2, 0);
+
+ expectAnswersValid(fd2, AF_INET, ns_r_nxdomain);
+ expectAnswersValid(fd1, AF_INET, ns_r_nxdomain);
+
+ fd1 = android_res_nquery(
+ NETWORK_UNSPECIFIED, "test3-nx.metric.gstatic.com",
+ ns_c_in, ns_t_aaaa, ANDROID_RESOLV_NO_CACHE_LOOKUP);
+ EXPECT_GE(fd1, 0);
+ fd2 = android_res_nquery(
+ NETWORK_UNSPECIFIED, "test4-nx.metric.gstatic.com",
+ ns_c_in, ns_t_aaaa, ANDROID_RESOLV_NO_CACHE_LOOKUP);
+ EXPECT_GE(fd2, 0);
+ expectAnswersValid(fd2, AF_INET6, ns_r_nxdomain);
+ expectAnswersValid(fd1, AF_INET6, ns_r_nxdomain);
+}
+
+TEST (NativeDnsAsyncTest, Async_Cancel) {
+ int fd = android_res_nquery(
+ NETWORK_UNSPECIFIED, "www.google.com", ns_c_in, ns_t_a, 0);
+ errno = 0;
+ android_res_cancel(fd);
+ int err = errno;
+ EXPECT_EQ(err, 0);
+ // DO NOT call cancel or result with the same fd more than once,
+ // otherwise it will hit fdsan double-close fd.
+}
+
+TEST (NativeDnsAsyncTest, Async_Query_MALFORMED) {
+ // Empty string to create BLOB and query, we will get empty result and rcode = 0
+ // on DNSTLS.
+ int fd = android_res_nquery(
+ NETWORK_UNSPECIFIED, "", ns_c_in, ns_t_a, 0);
+ EXPECT_GE(fd, 0);
+ expectAnswersValid(fd, AF_INET, ns_r_noerror);
+
+ std::string exceedingLabelQuery = "www." + std::string(70, 'g') + ".com";
+ std::string exceedingDomainQuery = "www." + std::string(255, 'g') + ".com";
+
+ fd = android_res_nquery(NETWORK_UNSPECIFIED,
+ exceedingLabelQuery.c_str(), ns_c_in, ns_t_a, 0);
+ EXPECT_EQ(-EMSGSIZE, fd);
+ fd = android_res_nquery(NETWORK_UNSPECIFIED,
+ exceedingDomainQuery.c_str(), ns_c_in, ns_t_a, 0);
+ EXPECT_EQ(-EMSGSIZE, fd);
+}
+
+TEST (NativeDnsAsyncTest, Async_Send_MALFORMED) {
+ uint8_t buf[10] = {};
+ // empty BLOB
+ int fd = android_res_nsend(NETWORK_UNSPECIFIED, buf, 10, 0);
+ EXPECT_GE(fd, 0);
+ expectAnswersNotValid(fd, -EINVAL);
+
+ std::vector<uint8_t> largeBuf(2 * MAXPACKET, 0);
+ // A buffer larger than 8KB
+ fd = android_res_nsend(
+ NETWORK_UNSPECIFIED, largeBuf.data(), largeBuf.size(), 0);
+ EXPECT_EQ(-EMSGSIZE, fd);
+
+ // 5000 bytes filled with 0. This returns EMSGSIZE because FrameworkListener limits the size of
+ // commands to 4096 bytes.
+ fd = android_res_nsend(NETWORK_UNSPECIFIED, largeBuf.data(), 5000, 0);
+ EXPECT_EQ(-EMSGSIZE, fd);
+
+ // 500 bytes filled with 0
+ fd = android_res_nsend(NETWORK_UNSPECIFIED, largeBuf.data(), 500, 0);
+ EXPECT_GE(fd, 0);
+ expectAnswersNotValid(fd, -EINVAL);
+
+ // 5000 bytes filled with 0xFF
+ std::vector<uint8_t> ffBuf(5000, 0xFF);
+ fd = android_res_nsend(
+ NETWORK_UNSPECIFIED, ffBuf.data(), ffBuf.size(), 0);
+ EXPECT_EQ(-EMSGSIZE, fd);
+
+ // 500 bytes filled with 0xFF
+ fd = android_res_nsend(NETWORK_UNSPECIFIED, ffBuf.data(), 500, 0);
+ EXPECT_GE(fd, 0);
+ expectAnswersNotValid(fd, -EINVAL);
+}
diff --git a/tests/cts/net/native/qtaguid/Android.bp b/tests/cts/net/native/qtaguid/Android.bp
new file mode 100644
index 0000000..c0f0613
--- /dev/null
+++ b/tests/cts/net/native/qtaguid/Android.bp
@@ -0,0 +1,53 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Build the unit tests.
+
+cc_test {
+ name: "CtsNativeNetTestCases",
+
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+
+ srcs: ["src/NativeQtaguidTest.cpp"],
+
+ shared_libs: [
+ "libutils",
+ "liblog",
+ ],
+
+ static_libs: [
+ "libgtest",
+ "libqtaguid",
+ ],
+
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+}
diff --git a/tests/cts/net/native/qtaguid/Android.mk b/tests/cts/net/native/qtaguid/Android.mk
deleted file mode 100644
index bf89e5f..0000000
--- a/tests/cts/net/native/qtaguid/Android.mk
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Build the unit tests.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CtsNativeNetTestCases
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-LOCAL_SRC_FILES := \
- src/NativeQtaguidTest.cpp
-
-LOCAL_SHARED_LIBRARIES := \
- libutils \
- liblog \
-
-LOCAL_STATIC_LIBRARIES := \
- libgtest \
- libqtaguid \
-
-LOCAL_CTS_TEST_PACKAGE := android.net.native
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts
-
-LOCAL_CFLAGS := -Werror -Wall
-
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/tests/cts/net/native/qtaguid/AndroidTest.xml b/tests/cts/net/native/qtaguid/AndroidTest.xml
index 7591c87..a55afe7 100644
--- a/tests/cts/net/native/qtaguid/AndroidTest.xml
+++ b/tests/cts/net/native/qtaguid/AndroidTest.xml
@@ -16,6 +16,8 @@
<configuration description="Config for CTS Native Network xt_qtaguid test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
<option name="push" value="CtsNativeNetTestCases->/data/local/tmp/CtsNativeNetTestCases" />
diff --git a/tests/cts/net/native/qtaguid/src/NativeQtaguidTest.cpp b/tests/cts/net/native/qtaguid/src/NativeQtaguidTest.cpp
index 1892a44..7dc6240 100644
--- a/tests/cts/net/native/qtaguid/src/NativeQtaguidTest.cpp
+++ b/tests/cts/net/native/qtaguid/src/NativeQtaguidTest.cpp
@@ -18,36 +18,29 @@
#include <error.h>
#include <errno.h>
#include <inttypes.h>
+#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
-#include <sys/utsname.h>
#include <gtest/gtest.h>
#include <qtaguid/qtaguid.h>
-int hasQtaguidKernelSupport() {
- struct utsname buf;
- int kernel_version_major;
- int kernel_version_minor;
-
- int ret = uname(&buf);
- if (ret) {
- ret = -errno;
- return ret;
- }
- char dummy;
- ret = sscanf(buf.release, "%d.%d%c", &kernel_version_major, &kernel_version_minor, &dummy);
- if (ret < 3)
- return -EINVAL;
-
- if ((kernel_version_major == 4 && kernel_version_minor < 9) ||
- (kernel_version_major < 4)) {
- return 1;
- } else {
- return access("/proc/net/xt_qtaguid/ctrl", F_OK) != -1;
- }
+int canAccessQtaguidFile() {
+ int fd = open("/proc/net/xt_qtaguid/ctrl", O_RDONLY | O_CLOEXEC);
+ close(fd);
+ return fd != -1;
}
+#define SKIP_IF_QTAGUID_NOT_SUPPORTED() \
+ do { \
+ int res = canAccessQtaguidFile(); \
+ ASSERT_LE(0, res); \
+ if (!res) { \
+ GTEST_LOG_(INFO) << "This test is skipped since kernel may not have the module\n"; \
+ return; \
+ } \
+ } while (0)
+
int getCtrlSkInfo(int tag, uid_t uid, uint64_t* sk_addr, int* ref_cnt) {
FILE *fp;
fp = fopen("/proc/net/xt_qtaguid/ctrl", "r");
@@ -95,12 +88,8 @@
}
TEST (NativeQtaguidTest, close_socket_without_untag) {
- int res = hasQtaguidKernelSupport();
- ASSERT_LE(0, res);
- if (!res) {
- GTEST_LOG_(INFO) << "This test is skipped since kernel may not have the module\n";
- return;
- }
+ SKIP_IF_QTAGUID_NOT_SUPPORTED();
+
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
uid_t uid = getuid();
int tag = arc4random();
@@ -114,12 +103,8 @@
}
TEST (NativeQtaguidTest, close_socket_without_untag_ipv6) {
- int res = hasQtaguidKernelSupport();
- ASSERT_LE(0, res);
- if (!res) {
- GTEST_LOG_(INFO) << "This test is skipped since kernel may not have the module\n";
- return;
- }
+ SKIP_IF_QTAGUID_NOT_SUPPORTED();
+
int sockfd = socket(AF_INET6, SOCK_STREAM, 0);
uid_t uid = getuid();
int tag = arc4random();
@@ -133,12 +118,8 @@
}
TEST (NativeQtaguidTest, no_socket_addr_leak) {
- int res = hasQtaguidKernelSupport();
- ASSERT_LE(0, res);
- if (!res) {
- GTEST_LOG_(INFO) << "This test is skipped since kernel may not have the module\n";
- return;
- }
+ SKIP_IF_QTAGUID_NOT_SUPPORTED();
+
checkNoSocketPointerLeaks(AF_INET);
checkNoSocketPointerLeaks(AF_INET6);
}
diff --git a/tests/cts/net/src/android/net/cts/AirplaneModeTest.java b/tests/cts/net/src/android/net/cts/AirplaneModeTest.java
index 0a3146c..524e549 100644
--- a/tests/cts/net/src/android/net/cts/AirplaneModeTest.java
+++ b/tests/cts/net/src/android/net/cts/AirplaneModeTest.java
@@ -18,13 +18,14 @@
import android.content.ContentResolver;
import android.content.Context;
-import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
import android.provider.Settings;
import android.test.AndroidTestCase;
import android.util.Log;
import java.lang.Thread;
+@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
public class AirplaneModeTest extends AndroidTestCase {
private static final String TAG = "AirplaneModeTest";
private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 810b5df..ca1f771 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -21,23 +21,34 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.cts.util.CtsNetUtils.ConnectivityActionReceiver;
+import static android.net.cts.util.CtsNetUtils.HTTP_PORT;
+import static android.net.cts.util.CtsNetUtils.NETWORK_CALLBACK_ACTION;
+import static android.net.cts.util.CtsNetUtils.TEST_HOST;
+import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.AF_UNSPEC;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import android.annotation.NonNull;
import android.app.Instrumentation;
import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
+import android.app.UiAutomation;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.IpSecManager;
+import android.net.IpSecManager.UdpEncapsulationSocket;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
@@ -46,31 +57,34 @@
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
import android.net.NetworkRequest;
+import android.net.SocketKeepalive;
+import android.net.cts.util.CtsNetUtils;
+import android.net.util.KeepaliveUtils;
import android.net.wifi.WifiManager;
+import android.os.Build;
import android.os.Looper;
+import android.os.MessageQueue;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.VintfRuntimeInfo;
+import android.platform.test.annotations.AppModeFull;
import android.provider.Settings;
-import android.system.Os;
-import android.system.OsConstants;
import android.test.AndroidTestCase;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import androidx.test.InstrumentationRegistry;
-import com.android.internal.R;
-import com.android.internal.telephony.PhoneConstants;
-
import libcore.io.Streams;
-import java.io.File;
-import java.io.FileNotFoundException;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
+import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -78,12 +92,14 @@
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
-import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -91,46 +107,33 @@
private static final String TAG = ConnectivityManagerTest.class.getSimpleName();
- private static final String FEATURE_ENABLE_HIPRI = "enableHIPRI";
-
public static final int TYPE_MOBILE = ConnectivityManager.TYPE_MOBILE;
public static final int TYPE_WIFI = ConnectivityManager.TYPE_WIFI;
private static final int HOST_ADDRESS = 0x7f000001;// represent ip 127.0.0.1
- private static final String TEST_HOST = "connectivitycheck.gstatic.com";
- private static final int SOCKET_TIMEOUT_MS = 2000;
- private static final int SEND_BROADCAST_TIMEOUT = 30000;
+ private static final int CONNECT_TIMEOUT_MS = 2000;
+ private static final int KEEPALIVE_CALLBACK_TIMEOUT_MS = 2000;
+ private static final int KEEPALIVE_SOCKET_TIMEOUT_MS = 5000;
+ private static final int INTERVAL_KEEPALIVE_RETRY_MS = 500;
+ private static final int MAX_KEEPALIVE_RETRY_COUNT = 3;
+ private static final int MIN_KEEPALIVE_INTERVAL = 10;
private static final int NETWORK_CHANGE_METEREDNESS_TIMEOUT = 5000;
private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20;
private static final long INTERVAL_MULTIPATH_PREF_CHECK_MS = 500;
- private static final int HTTP_PORT = 80;
- private static final String HTTP_REQUEST =
- "GET /generate_204 HTTP/1.0\r\n" +
- "Host: " + TEST_HOST + "\r\n" +
- "Connection: keep-alive\r\n\r\n";
-
- // Base path for IPv6 sysctls
- private static final String IPV6_SYSCTL_DIR = "/proc/sys/net/ipv6/conf";
-
- // Expected values for MIN|MAX_PLEN.
- private static final int IPV6_WIFI_ACCEPT_RA_RT_INFO_MIN_PLEN = 48;
- private static final int IPV6_WIFI_ACCEPT_RA_RT_INFO_MAX_PLEN = 64;
-
- // Expected values for RFC 7559 router soliciations.
- // Maximum number of router solicitations to send. -1 means no limit.
- private static final int IPV6_WIFI_ROUTER_SOLICITATIONS = -1;
-
- // Action sent to ConnectivityActionReceiver when a network callback is sent via PendingIntent.
- private static final String NETWORK_CALLBACK_ACTION =
- "ConnectivityManagerTest.NetworkCallbackAction";
-
- // Intent string to get the number of wifi CONNECTIVITY_ACTION callbacks the test app has seen
- public static final String GET_WIFI_CONNECTIVITY_ACTION_COUNT =
- "android.net.cts.appForApi23.getWifiConnectivityActionCount";
-
// device could have only one interface: data, wifi.
private static final int MIN_NUM_NETWORK_TYPES = 1;
+ // Minimum supported keepalive counts for wifi and cellular.
+ public static final int MIN_SUPPORTED_CELLULAR_KEEPALIVE_COUNT = 1;
+ public static final int MIN_SUPPORTED_WIFI_KEEPALIVE_COUNT = 3;
+
+ private static final String NETWORK_METERED_MULTIPATH_PREFERENCE_RES_NAME =
+ "config_networkMeteredMultipathPreference";
+ private static final String KEEPALIVE_ALLOWED_UNPRIVILEGED_RES_NAME =
+ "config_allowedUnprivilegedKeepalivePerUid";
+ private static final String KEEPALIVE_RESERVED_PER_SLOT_RES_NAME =
+ "config_reservedPrivilegedKeepaliveSlots";
+
private Context mContext;
private Instrumentation mInstrumentation;
private ConnectivityManager mCm;
@@ -139,8 +142,9 @@
private final HashMap<Integer, NetworkConfig> mNetworks =
new HashMap<Integer, NetworkConfig>();
boolean mWifiConnectAttempted;
- private TestNetworkCallback mCellNetworkCallback;
-
+ private UiAutomation mUiAutomation;
+ private CtsNetUtils mCtsNetUtils;
+ private boolean mShellPermissionIdentityAdopted;
@Override
protected void setUp() throws Exception {
@@ -151,6 +155,7 @@
mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mPackageManager = mContext.getPackageManager();
+ mCtsNetUtils = new CtsNetUtils(mContext);
mWifiConnectAttempted = false;
// Get com.android.internal.R.array.networkAttributes
@@ -167,17 +172,20 @@
mNetworks.put(n.type, n);
} catch (Exception e) {}
}
+ mUiAutomation = mInstrumentation.getUiAutomation();
+ mShellPermissionIdentityAdopted = false;
}
@Override
protected void tearDown() throws Exception {
// Return WiFi to its original disabled state after tests that explicitly connect.
if (mWifiConnectAttempted) {
- disconnectFromWifi(null);
+ mCtsNetUtils.disconnectFromWifi(null);
}
- if (cellConnectAttempted()) {
- disconnectFromCell();
+ if (mCtsNetUtils.cellConnectAttempted()) {
+ mCtsNetUtils.disconnectFromCell();
}
+ dropShellPermissionIdentity();
super.tearDown();
}
@@ -188,10 +196,10 @@
*/
private Network ensureWifiConnected() {
if (mWifiManager.isWifiEnabled()) {
- return getWifiNetwork();
+ return mCtsNetUtils.getWifiNetwork();
}
mWifiConnectAttempted = true;
- return connectToWifi();
+ return mCtsNetUtils.connectToWifi();
}
public void testIsNetworkTypeValid() {
@@ -289,6 +297,7 @@
* Tests that connections can be opened on WiFi and cellphone networks,
* and that they are made from different IP addresses.
*/
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public void testOpenConnection() throws Exception {
boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI)
&& mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
@@ -298,8 +307,8 @@
return;
}
- Network wifiNetwork = connectToWifi();
- Network cellNetwork = connectToCell();
+ Network wifiNetwork = mCtsNetUtils.connectToWifi();
+ Network cellNetwork = mCtsNetUtils.connectToCell();
// This server returns the requestor's IP address as the response body.
URL url = new URL("http://google-ipv6test.appspot.com/ip.js?fmt=text");
String wifiAddressString = httpGet(wifiNetwork, url);
@@ -317,33 +326,6 @@
assertFalse("Unexpectedly equal: " + wifiNetwork, wifiNetwork.equals(cellNetwork));
}
- private Network connectToCell() throws InterruptedException {
- if (cellConnectAttempted()) {
- throw new IllegalStateException("Already connected");
- }
- NetworkRequest cellRequest = new NetworkRequest.Builder()
- .addTransportType(TRANSPORT_CELLULAR)
- .addCapability(NET_CAPABILITY_INTERNET)
- .build();
- mCellNetworkCallback = new TestNetworkCallback();
- mCm.requestNetwork(cellRequest, mCellNetworkCallback);
- final Network cellNetwork = mCellNetworkCallback.waitForAvailable();
- assertNotNull("Cell network not available within timeout", cellNetwork);
- return cellNetwork;
- }
-
- private boolean cellConnectAttempted() {
- return mCellNetworkCallback != null;
- }
-
- private void disconnectFromCell() {
- if (!cellConnectAttempted()) {
- throw new IllegalStateException("Cell connection not attempted");
- }
- mCm.unregisterNetworkCallback(mCellNetworkCallback);
- mCellNetworkCallback = null;
- }
-
/**
* Performs a HTTP GET to the specified URL on the specified Network, and returns
* the response body decoded as UTF-8.
@@ -399,9 +381,6 @@
final String invalidateFeature = "invalidateFeature";
final String mmsFeature = "enableMMS";
- final int failureCode = -1;
- final int wifiOnlyStartFailureCode = PhoneConstants.APN_REQUEST_FAILED;
- final int wifiOnlyStopFailureCode = -1;
assertStartUsingNetworkFeatureUnsupported(TYPE_MOBILE, invalidateFeature);
assertStopUsingNetworkFeatureUnsupported(TYPE_MOBILE, invalidateFeature);
@@ -451,6 +430,7 @@
* WiFi. We could add a version that uses the telephony data connection but it's not clear
* that it would increase test coverage by much (how many devices have 3G radio but not Wifi?).
*/
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public void testRegisterNetworkCallback() {
if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi");
@@ -491,6 +471,7 @@
* {@link #testRegisterNetworkCallback} except that a {@code PendingIntent} is used instead
* of a {@code NetworkCallback}.
*/
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public void testRegisterNetworkCallback_withPendingIntent() {
if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi");
@@ -503,7 +484,7 @@
filter.addAction(NETWORK_CALLBACK_ACTION);
ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
- ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
+ mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
mContext.registerReceiver(receiver, filter);
// Create a broadcast PendingIntent for NETWORK_CALLBACK_ACTION.
@@ -535,6 +516,7 @@
* Exercises the requestNetwork with NetworkCallback API. This checks to
* see if we get a callback for an INTERNET request.
*/
+ @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
public void testRequestNetworkCallback() {
final TestNetworkCallback callback = new TestNetworkCallback();
mCm.requestNetwork(new NetworkRequest.Builder()
@@ -557,10 +539,11 @@
* Exercises the requestNetwork with NetworkCallback API with timeout - expected to
* fail. Use WIFI and switch Wi-Fi off.
*/
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public void testRequestNetworkCallback_onUnavailable() {
final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
if (previousWifiEnabledState) {
- disconnectFromWifi(null);
+ mCtsNetUtils.disconnectFromWifi(null);
}
final TestNetworkCallback callback = new TestNetworkCallback();
@@ -577,323 +560,23 @@
} finally {
mCm.unregisterNetworkCallback(callback);
if (previousWifiEnabledState) {
- connectToWifi();
+ mCtsNetUtils.connectToWifi();
}
}
}
- /**
- * Tests reporting of connectivity changed.
- */
- public void testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent() {
- if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
- Log.i(TAG, "testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent cannot execute unless device supports WiFi");
- return;
- }
- ConnectivityReceiver.prepare();
-
- toggleWifi();
-
- // The connectivity broadcast has been sent; push through a terminal broadcast
- // to wait for in the receive to confirm it didn't see the connectivity change.
- Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
- finalIntent.setClass(mContext, ConnectivityReceiver.class);
- mContext.sendBroadcast(finalIntent);
- assertFalse(ConnectivityReceiver.waitForBroadcast());
- }
-
- public void testConnectivityChanged_whenRegistered_shouldReceiveIntent() {
- if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
- Log.i(TAG, "testConnectivityChanged_whenRegistered_shouldReceiveIntent cannot execute unless device supports WiFi");
- return;
- }
- ConnectivityReceiver.prepare();
- ConnectivityReceiver receiver = new ConnectivityReceiver();
- IntentFilter filter = new IntentFilter();
- filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- mContext.registerReceiver(receiver, filter);
-
- toggleWifi();
- Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
- finalIntent.setClass(mContext, ConnectivityReceiver.class);
- mContext.sendBroadcast(finalIntent);
-
- assertTrue(ConnectivityReceiver.waitForBroadcast());
- }
-
- public void testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent()
- throws InterruptedException {
- if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
- Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
- return;
- }
- mContext.startActivity(new Intent()
- .setComponent(new ComponentName("android.net.cts.appForApi23",
- "android.net.cts.appForApi23.ConnectivityListeningActivity"))
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
- Thread.sleep(200);
-
- toggleWifi();
-
- Intent getConnectivityCount = new Intent(GET_WIFI_CONNECTIVITY_ACTION_COUNT);
- assertEquals(2, sendOrderedBroadcastAndReturnResultCode(
- getConnectivityCount, SEND_BROADCAST_TIMEOUT));
- }
-
- private int sendOrderedBroadcastAndReturnResultCode(
- Intent intent, int timeoutMs) throws InterruptedException {
- final LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<>(1);
- mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- result.offer(getResultCode());
- }
- }, null, 0, null, null);
-
- Integer resultCode = result.poll(timeoutMs, TimeUnit.MILLISECONDS);
- assertNotNull("Timed out (more than " + timeoutMs +
- " milliseconds) waiting for result code for broadcast", resultCode);
- return resultCode;
- }
-
- // Toggle WiFi twice, leaving it in the state it started in
- private void toggleWifi() {
- if (mWifiManager.isWifiEnabled()) {
- Network wifiNetwork = getWifiNetwork();
- disconnectFromWifi(wifiNetwork);
- connectToWifi();
- } else {
- connectToWifi();
- Network wifiNetwork = getWifiNetwork();
- disconnectFromWifi(wifiNetwork);
- }
- }
-
- /** Enable WiFi and wait for it to become connected to a network. */
- private Network connectToWifi() {
- final TestNetworkCallback callback = new TestNetworkCallback();
- mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
- Network wifiNetwork = null;
-
- ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
- ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
- IntentFilter filter = new IntentFilter();
- filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- mContext.registerReceiver(receiver, filter);
-
- boolean connected = false;
- try {
- assertTrue(mWifiManager.setWifiEnabled(true));
- // Ensure we get both an onAvailable callback and a CONNECTIVITY_ACTION.
- wifiNetwork = callback.waitForAvailable();
- assertNotNull(wifiNetwork);
- connected = receiver.waitForState();
- } catch (InterruptedException ex) {
- fail("connectToWifi was interrupted");
- } finally {
- mCm.unregisterNetworkCallback(callback);
- mContext.unregisterReceiver(receiver);
- }
-
- assertTrue("Wifi must be configured to connect to an access point for this test.",
- connected);
- return wifiNetwork;
- }
-
- private Socket getBoundSocket(Network network, String host, int port) throws IOException {
- InetSocketAddress addr = new InetSocketAddress(host, port);
- Socket s = network.getSocketFactory().createSocket();
- try {
- s.setSoTimeout(SOCKET_TIMEOUT_MS);
- s.connect(addr, SOCKET_TIMEOUT_MS);
- } catch (IOException e) {
- s.close();
- throw e;
- }
- return s;
- }
-
- private void testHttpRequest(Socket s) throws IOException {
- OutputStream out = s.getOutputStream();
- InputStream in = s.getInputStream();
-
- final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8");
- byte[] responseBytes = new byte[4096];
- out.write(requestBytes);
- in.read(responseBytes);
- assertTrue(new String(responseBytes, "UTF-8").startsWith("HTTP/1.0 204 No Content\r\n"));
- }
-
- /** Disable WiFi and wait for it to become disconnected from the network. */
- private void disconnectFromWifi(Network wifiNetworkToCheck) {
- final TestNetworkCallback callback = new TestNetworkCallback();
- mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
- Network lostWifiNetwork = null;
-
- ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
- ConnectivityManager.TYPE_WIFI, NetworkInfo.State.DISCONNECTED);
- IntentFilter filter = new IntentFilter();
- filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- mContext.registerReceiver(receiver, filter);
-
- // Assert that we can establish a TCP connection on wifi.
- Socket wifiBoundSocket = null;
- if (wifiNetworkToCheck != null) {
- try {
- wifiBoundSocket = getBoundSocket(wifiNetworkToCheck, TEST_HOST, HTTP_PORT);
- testHttpRequest(wifiBoundSocket);
- } catch (IOException e) {
- fail("HTTP request before wifi disconnected failed with: " + e);
+ private InetAddress getFirstV4Address(Network network) {
+ LinkProperties linkProperties = mCm.getLinkProperties(network);
+ for (InetAddress address : linkProperties.getAddresses()) {
+ if (address instanceof Inet4Address) {
+ return address;
}
}
-
- boolean disconnected = false;
- try {
- assertTrue(mWifiManager.setWifiEnabled(false));
- // Ensure we get both an onLost callback and a CONNECTIVITY_ACTION.
- lostWifiNetwork = callback.waitForLost();
- assertNotNull(lostWifiNetwork);
- disconnected = receiver.waitForState();
- } catch (InterruptedException ex) {
- fail("disconnectFromWifi was interrupted");
- } finally {
- mCm.unregisterNetworkCallback(callback);
- mContext.unregisterReceiver(receiver);
- }
-
- assertTrue("Wifi failed to reach DISCONNECTED state.", disconnected);
-
- // Check that the socket is closed when wifi disconnects.
- if (wifiBoundSocket != null) {
- try {
- testHttpRequest(wifiBoundSocket);
- fail("HTTP request should not succeed after wifi disconnects");
- } catch (IOException expected) {
- assertEquals(Os.strerror(OsConstants.ECONNABORTED), expected.getMessage());
- }
- }
- }
-
- /**
- * Receiver that captures the last connectivity change's network type and state. Recognizes
- * both {@code CONNECTIVITY_ACTION} and {@code NETWORK_CALLBACK_ACTION} intents.
- */
- private class ConnectivityActionReceiver extends BroadcastReceiver {
-
- private final CountDownLatch mReceiveLatch = new CountDownLatch(1);
-
- private final int mNetworkType;
- private final NetworkInfo.State mNetState;
-
- ConnectivityActionReceiver(int networkType, NetworkInfo.State netState) {
- mNetworkType = networkType;
- mNetState = netState;
- }
-
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- NetworkInfo networkInfo = null;
-
- // When receiving ConnectivityManager.CONNECTIVITY_ACTION, the NetworkInfo parcelable
- // is stored in EXTRA_NETWORK_INFO. With a NETWORK_CALLBACK_ACTION, the Network is
- // sent in EXTRA_NETWORK and we need to ask the ConnectivityManager for the NetworkInfo.
- if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
- networkInfo = intent.getExtras()
- .getParcelable(ConnectivityManager.EXTRA_NETWORK_INFO);
- assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK_INFO", networkInfo);
- } else if (NETWORK_CALLBACK_ACTION.equals(action)) {
- Network network = intent.getExtras()
- .getParcelable(ConnectivityManager.EXTRA_NETWORK);
- assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK", network);
- networkInfo = mCm.getNetworkInfo(network);
- if (networkInfo == null) {
- // When disconnecting, it seems like we get an intent sent with an invalid
- // Network; that is, by the time we call ConnectivityManager.getNetworkInfo(),
- // it is invalid. Ignore these.
- Log.i(TAG, "ConnectivityActionReceiver NETWORK_CALLBACK_ACTION ignoring "
- + "invalid network");
- return;
- }
- } else {
- fail("ConnectivityActionReceiver received unxpected intent action: " + action);
- }
-
- assertNotNull("ConnectivityActionReceiver didn't find NetworkInfo", networkInfo);
- int networkType = networkInfo.getType();
- State networkState = networkInfo.getState();
- Log.i(TAG, "Network type: " + networkType + " state: " + networkState);
- if (networkType == mNetworkType && networkInfo.getState() == mNetState) {
- mReceiveLatch.countDown();
- }
- }
-
- public boolean waitForState() throws InterruptedException {
- return mReceiveLatch.await(30, TimeUnit.SECONDS);
- }
- }
-
- /**
- * Callback used in testRegisterNetworkCallback that allows caller to block on
- * {@code onAvailable}.
- */
- private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
- private final CountDownLatch mAvailableLatch = new CountDownLatch(1);
- private final CountDownLatch mLostLatch = new CountDownLatch(1);
- private final CountDownLatch mUnavailableLatch = new CountDownLatch(1);
-
- public Network currentNetwork;
- public Network lastLostNetwork;
-
- public Network waitForAvailable() throws InterruptedException {
- return mAvailableLatch.await(30, TimeUnit.SECONDS) ? currentNetwork : null;
- }
-
- public Network waitForLost() throws InterruptedException {
- return mLostLatch.await(30, TimeUnit.SECONDS) ? lastLostNetwork : null;
- }
-
- public boolean waitForUnavailable() throws InterruptedException {
- return mUnavailableLatch.await(2, TimeUnit.SECONDS);
- }
-
-
- @Override
- public void onAvailable(Network network) {
- currentNetwork = network;
- mAvailableLatch.countDown();
- }
-
- @Override
- public void onLost(Network network) {
- lastLostNetwork = network;
- if (network.equals(currentNetwork)) {
- currentNetwork = null;
- }
- mLostLatch.countDown();
- }
-
- @Override
- public void onUnavailable() {
- mUnavailableLatch.countDown();
- }
- }
-
- private Network getWifiNetwork() {
- TestNetworkCallback callback = new TestNetworkCallback();
- mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
- Network network = null;
- try {
- network = callback.waitForAvailable();
- } catch (InterruptedException e) {
- fail("NetworkCallback wait was interrupted.");
- } finally {
- mCm.unregisterNetworkCallback(callback);
- }
- assertNotNull("Cannot find Network for wifi. Is wifi connected?", network);
- return network;
+ return null;
}
/** Verify restricted networks cannot be requested. */
+ @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
public void testRestrictedNetworks() {
// Verify we can request unrestricted networks:
NetworkRequest request = new NetworkRequest.Builder()
@@ -910,60 +593,6 @@
} catch (SecurityException expected) {}
}
- private Scanner makeWifiSysctlScanner(String key) throws FileNotFoundException {
- Network network = ensureWifiConnected();
- String iface = mCm.getLinkProperties(network).getInterfaceName();
- String path = IPV6_SYSCTL_DIR + "/" + iface + "/" + key;
- return new Scanner(new File(path));
- }
-
- /** Verify that accept_ra_rt_info_min_plen exists and is set to the expected value */
- public void testAcceptRaRtInfoMinPlen() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
- return;
- }
- Scanner s = makeWifiSysctlScanner("accept_ra_rt_info_min_plen");
- assertEquals(IPV6_WIFI_ACCEPT_RA_RT_INFO_MIN_PLEN, s.nextInt());
- }
-
- /** Verify that accept_ra_rt_info_max_plen exists and is set to the expected value */
- public void testAcceptRaRtInfoMaxPlen() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
- return;
- }
- Scanner s = makeWifiSysctlScanner("accept_ra_rt_info_max_plen");
- assertEquals(IPV6_WIFI_ACCEPT_RA_RT_INFO_MAX_PLEN, s.nextInt());
- }
-
- /** Verify that router_solicitations exists and is set to the expected value */
- public void testRouterSolicitations() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
- return;
- }
- Scanner s = makeWifiSysctlScanner("router_solicitations");
- assertEquals(IPV6_WIFI_ROUTER_SOLICITATIONS, s.nextInt());
- }
-
- /** Verify that router_solicitation_max_interval exists and is in an acceptable interval */
- public void testRouterSolicitationMaxInterval() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
- return;
- }
- Scanner s = makeWifiSysctlScanner("router_solicitation_max_interval");
- int interval = s.nextInt();
- // Verify we're in the interval [15 minutes, 60 minutes]. Lower values may adversely
- // impact battery life and higher values can decrease the probability of detecting
- // network changes.
- final int lowerBoundSec = 15 * 60;
- final int upperBoundSec = 60 * 60;
- assertTrue(lowerBoundSec <= interval);
- assertTrue(interval <= upperBoundSec);
- }
-
// Returns "true", "false" or "none"
private String getWifiMeteredStatus(String ssid) throws Exception {
// Interestingly giving the SSID as an argument to list wifi-networks
@@ -1045,7 +674,7 @@
final String rawMeteredPref = Settings.Global.getString(resolver,
NETWORK_METERED_MULTIPATH_PREFERENCE);
return TextUtils.isEmpty(rawMeteredPref)
- ? mContext.getResources().getInteger(R.integer.config_networkMeteredMultipathPreference)
+ ? getIntResourceForName(NETWORK_METERED_MULTIPATH_PREFERENCE_RES_NAME)
: Integer.parseInt(rawMeteredPref);
}
@@ -1065,6 +694,7 @@
* Verify that getMultipathPreference does return appropriate values
* for metered and unmetered networks.
*/
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public void testGetMultipathPreference() throws Exception {
final ContentResolver resolver = mContext.getContentResolver();
final Network network = ensureWifiConnected();
@@ -1105,4 +735,531 @@
setWifiMeteredStatus(ssid, oldMeteredSetting);
}
}
+
+ // TODO: move the following socket keep alive test to dedicated test class.
+ /**
+ * Callback used in tcp keepalive offload that allows caller to wait callback fires.
+ */
+ private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback {
+ public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
+
+ public static class CallbackValue {
+ public final CallbackType callbackType;
+ public final int error;
+
+ private CallbackValue(final CallbackType type, final int error) {
+ this.callbackType = type;
+ this.error = error;
+ }
+
+ public static class OnStartedCallback extends CallbackValue {
+ OnStartedCallback() { super(CallbackType.ON_STARTED, 0); }
+ }
+
+ public static class OnStoppedCallback extends CallbackValue {
+ OnStoppedCallback() { super(CallbackType.ON_STOPPED, 0); }
+ }
+
+ public static class OnErrorCallback extends CallbackValue {
+ OnErrorCallback(final int error) { super(CallbackType.ON_ERROR, error); }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o.getClass() == this.getClass()
+ && this.callbackType == ((CallbackValue) o).callbackType
+ && this.error == ((CallbackValue) o).error;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, error);
+ }
+ }
+
+ private final LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
+
+ @Override
+ public void onStarted() {
+ mCallbacks.add(new CallbackValue.OnStartedCallback());
+ }
+
+ @Override
+ public void onStopped() {
+ mCallbacks.add(new CallbackValue.OnStoppedCallback());
+ }
+
+ @Override
+ public void onError(final int error) {
+ mCallbacks.add(new CallbackValue.OnErrorCallback(error));
+ }
+
+ public CallbackValue pollCallback() {
+ try {
+ return mCallbacks.poll(KEEPALIVE_CALLBACK_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail("Callback not seen after " + KEEPALIVE_CALLBACK_TIMEOUT_MS + " ms");
+ }
+ return null;
+ }
+ private void expectCallback(CallbackValue expectedCallback) {
+ final CallbackValue actualCallback = pollCallback();
+ assertEquals(expectedCallback, actualCallback);
+ }
+
+ public void expectStarted() {
+ expectCallback(new CallbackValue.OnStartedCallback());
+ }
+
+ public void expectStopped() {
+ expectCallback(new CallbackValue.OnStoppedCallback());
+ }
+
+ public void expectError(int error) {
+ expectCallback(new CallbackValue.OnErrorCallback(error));
+ }
+ }
+
+ private InetAddress getAddrByName(final String hostname, final int family) throws Exception {
+ final InetAddress[] allAddrs = InetAddress.getAllByName(hostname);
+ for (InetAddress addr : allAddrs) {
+ if (family == AF_INET && addr instanceof Inet4Address) return addr;
+
+ if (family == AF_INET6 && addr instanceof Inet6Address) return addr;
+
+ if (family == AF_UNSPEC) return addr;
+ }
+ return null;
+ }
+
+ private Socket getConnectedSocket(final Network network, final String host, final int port,
+ final int socketTimeOut, final int family) throws Exception {
+ final Socket s = network.getSocketFactory().createSocket();
+ try {
+ final InetAddress addr = getAddrByName(host, family);
+ if (addr == null) fail("Fail to get destination address for " + family);
+
+ final InetSocketAddress sockAddr = new InetSocketAddress(addr, port);
+ s.setSoTimeout(socketTimeOut);
+ s.connect(sockAddr, CONNECT_TIMEOUT_MS);
+ } catch (Exception e) {
+ s.close();
+ throw e;
+ }
+ return s;
+ }
+
+ private int getSupportedKeepalivesForNet(@NonNull Network network) throws Exception {
+ final NetworkCapabilities nc = mCm.getNetworkCapabilities(network);
+
+ // Get number of supported concurrent keepalives for testing network.
+ final int[] keepalivesPerTransport = KeepaliveUtils.getSupportedKeepalives(mContext);
+ return KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
+ keepalivesPerTransport, nc);
+ }
+
+ private void adoptShellPermissionIdentity() {
+ mUiAutomation.adoptShellPermissionIdentity();
+ mShellPermissionIdentityAdopted = true;
+ }
+
+ private void dropShellPermissionIdentity() {
+ if (mShellPermissionIdentityAdopted) {
+ mUiAutomation.dropShellPermissionIdentity();
+ mShellPermissionIdentityAdopted = false;
+ }
+ }
+
+ private static boolean isTcpKeepaliveSupportedByKernel() {
+ final String kVersionString = VintfRuntimeInfo.getKernelVersion();
+ return compareMajorMinorVersion(kVersionString, "4.8") >= 0;
+ }
+
+ private static Pair<Integer, Integer> getVersionFromString(String version) {
+ // Only gets major and minor number of the version string.
+ final Pattern versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*");
+ final Matcher m = versionPattern.matcher(version);
+ if (m.matches()) {
+ final int major = Integer.parseInt(m.group(1));
+ final int minor = TextUtils.isEmpty(m.group(3)) ? 0 : Integer.parseInt(m.group(3));
+ return new Pair<>(major, minor);
+ } else {
+ return new Pair<>(0, 0);
+ }
+ }
+
+ // TODO: Move to util class.
+ private static int compareMajorMinorVersion(final String s1, final String s2) {
+ final Pair<Integer, Integer> v1 = getVersionFromString(s1);
+ final Pair<Integer, Integer> v2 = getVersionFromString(s2);
+
+ if (v1.first == v2.first) {
+ return Integer.compare(v1.second, v2.second);
+ } else {
+ return Integer.compare(v1.first, v2.first);
+ }
+ }
+
+ /**
+ * Verifies that version string compare logic returns expected result for various cases.
+ * Note that only major and minor number are compared.
+ */
+ public void testMajorMinorVersionCompare() {
+ assertEquals(0, compareMajorMinorVersion("4.8.1", "4.8"));
+ assertEquals(1, compareMajorMinorVersion("4.9", "4.8.1"));
+ assertEquals(1, compareMajorMinorVersion("5.0", "4.8"));
+ assertEquals(1, compareMajorMinorVersion("5", "4.8"));
+ assertEquals(0, compareMajorMinorVersion("5", "5.0"));
+ assertEquals(1, compareMajorMinorVersion("5-beta1", "4.8"));
+ assertEquals(0, compareMajorMinorVersion("4.8.0.0", "4.8"));
+ assertEquals(0, compareMajorMinorVersion("4.8-RC1", "4.8"));
+ assertEquals(0, compareMajorMinorVersion("4.8", "4.8"));
+ assertEquals(-1, compareMajorMinorVersion("3.10", "4.8.0"));
+ assertEquals(-1, compareMajorMinorVersion("4.7.10.10", "4.8"));
+ }
+
+ /**
+ * Verifies that the keepalive API cannot create any keepalive when the maximum number of
+ * keepalives is set to 0.
+ */
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ public void testKeepaliveWifiUnsupported() throws Exception {
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+ Log.i(TAG, "testKeepaliveUnsupported cannot execute unless device"
+ + " supports WiFi");
+ return;
+ }
+
+ final Network network = ensureWifiConnected();
+ if (getSupportedKeepalivesForNet(network) != 0) return;
+
+ adoptShellPermissionIdentity();
+
+ assertEquals(0, createConcurrentSocketKeepalives(network, 1, 0));
+ assertEquals(0, createConcurrentSocketKeepalives(network, 0, 1));
+
+ dropShellPermissionIdentity();
+ }
+
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ public void testCreateTcpKeepalive() throws Exception {
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+ Log.i(TAG, "testCreateTcpKeepalive cannot execute unless device supports WiFi");
+ return;
+ }
+
+ adoptShellPermissionIdentity();
+
+ final Network network = ensureWifiConnected();
+ if (getSupportedKeepalivesForNet(network) == 0) return;
+ // If kernel < 4.8 then it doesn't support TCP keepalive, but it might still support
+ // NAT-T keepalive. If keepalive limits from resource overlay is not zero, TCP keepalive
+ // needs to be supported except if the kernel doesn't support it.
+ if (!isTcpKeepaliveSupportedByKernel()) {
+ // Sanity check to ensure the callback result is expected.
+ assertEquals(0, createConcurrentSocketKeepalives(network, 0, 1));
+ Log.i(TAG, "testCreateTcpKeepalive is skipped for kernel "
+ + VintfRuntimeInfo.getKernelVersion());
+ return;
+ }
+
+ final byte[] requestBytes = CtsNetUtils.HTTP_REQUEST.getBytes("UTF-8");
+ // So far only ipv4 tcp keepalive offload is supported.
+ // TODO: add test case for ipv6 tcp keepalive offload when it is supported.
+ try (Socket s = getConnectedSocket(network, TEST_HOST, HTTP_PORT,
+ KEEPALIVE_SOCKET_TIMEOUT_MS, AF_INET)) {
+
+ // Should able to start keep alive offload when socket is idle.
+ final Executor executor = mContext.getMainExecutor();
+ final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
+ try (SocketKeepalive sk = mCm.createSocketKeepalive(network, s, executor, callback)) {
+ sk.start(MIN_KEEPALIVE_INTERVAL);
+ callback.expectStarted();
+
+ // App should not able to write during keepalive offload.
+ final OutputStream out = s.getOutputStream();
+ try {
+ out.write(requestBytes);
+ fail("Should not able to write");
+ } catch (IOException e) { }
+ // App should not able to read during keepalive offload.
+ final InputStream in = s.getInputStream();
+ byte[] responseBytes = new byte[4096];
+ try {
+ in.read(responseBytes);
+ fail("Should not able to read");
+ } catch (IOException e) { }
+
+ // Stop.
+ sk.stop();
+ callback.expectStopped();
+ }
+
+ // Ensure socket is still connected.
+ assertTrue(s.isConnected());
+ assertFalse(s.isClosed());
+
+ // Let socket be not idle.
+ try {
+ final OutputStream out = s.getOutputStream();
+ out.write(requestBytes);
+ } catch (IOException e) {
+ fail("Failed to write data " + e);
+ }
+ // Make sure response data arrives.
+ final MessageQueue fdHandlerQueue = Looper.getMainLooper().getQueue();
+ final FileDescriptor fd = s.getFileDescriptor$();
+ final CountDownLatch mOnReceiveLatch = new CountDownLatch(1);
+ fdHandlerQueue.addOnFileDescriptorEventListener(fd, EVENT_INPUT, (readyFd, events) -> {
+ mOnReceiveLatch.countDown();
+ return 0; // Unregister listener.
+ });
+ if (!mOnReceiveLatch.await(2, TimeUnit.SECONDS)) {
+ fdHandlerQueue.removeOnFileDescriptorEventListener(fd);
+ fail("Timeout: no response data");
+ }
+
+ // Should get ERROR_SOCKET_NOT_IDLE because there is still data in the receive queue
+ // that has not been read.
+ try (SocketKeepalive sk = mCm.createSocketKeepalive(network, s, executor, callback)) {
+ sk.start(MIN_KEEPALIVE_INTERVAL);
+ callback.expectError(SocketKeepalive.ERROR_SOCKET_NOT_IDLE);
+ }
+ }
+ }
+
+ private ArrayList<SocketKeepalive> createConcurrentKeepalivesOfType(
+ int requestCount, @NonNull TestSocketKeepaliveCallback callback,
+ Supplier<SocketKeepalive> kaFactory) {
+ final ArrayList<SocketKeepalive> kalist = new ArrayList<>();
+
+ int remainingRetries = MAX_KEEPALIVE_RETRY_COUNT;
+
+ // Test concurrent keepalives with the given supplier.
+ while (kalist.size() < requestCount) {
+ final SocketKeepalive ka = kaFactory.get();
+ ka.start(MIN_KEEPALIVE_INTERVAL);
+ TestSocketKeepaliveCallback.CallbackValue cv = callback.pollCallback();
+ assertNotNull(cv);
+ if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_ERROR) {
+ if (kalist.size() == 0 && cv.error == SocketKeepalive.ERROR_UNSUPPORTED) {
+ // Unsupported.
+ break;
+ } else if (cv.error == SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES) {
+ // Limit reached or temporary unavailable due to stopped slot is not yet
+ // released.
+ if (remainingRetries > 0) {
+ SystemClock.sleep(INTERVAL_KEEPALIVE_RETRY_MS);
+ remainingRetries--;
+ continue;
+ }
+ break;
+ }
+ }
+ if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_STARTED) {
+ kalist.add(ka);
+ } else {
+ fail("Unexpected error when creating " + (kalist.size() + 1) + " "
+ + ka.getClass().getSimpleName() + ": " + cv);
+ }
+ }
+
+ return kalist;
+ }
+
+ private @NonNull ArrayList<SocketKeepalive> createConcurrentNattSocketKeepalives(
+ @NonNull Network network, int requestCount,
+ @NonNull TestSocketKeepaliveCallback callback) throws Exception {
+
+ final Executor executor = mContext.getMainExecutor();
+
+ // Initialize a real NaT-T socket.
+ final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
+ final UdpEncapsulationSocket nattSocket = mIpSec.openUdpEncapsulationSocket();
+ final InetAddress srcAddr = getFirstV4Address(network);
+ final InetAddress dstAddr = getAddrByName(TEST_HOST, AF_INET);
+ assertNotNull(srcAddr);
+ assertNotNull(dstAddr);
+
+ // Test concurrent Nat-T keepalives.
+ final ArrayList<SocketKeepalive> result = createConcurrentKeepalivesOfType(requestCount,
+ callback, () -> mCm.createSocketKeepalive(network, nattSocket,
+ srcAddr, dstAddr, executor, callback));
+
+ nattSocket.close();
+ return result;
+ }
+
+ private @NonNull ArrayList<SocketKeepalive> createConcurrentTcpSocketKeepalives(
+ @NonNull Network network, int requestCount,
+ @NonNull TestSocketKeepaliveCallback callback) {
+ final Executor executor = mContext.getMainExecutor();
+
+ // Create concurrent TCP keepalives.
+ return createConcurrentKeepalivesOfType(requestCount, callback, () -> {
+ // Assert that TCP connections can be established. The file descriptor of tcp
+ // sockets will be duplicated and kept valid in service side if the keepalives are
+ // successfully started.
+ try (Socket tcpSocket = getConnectedSocket(network, TEST_HOST, HTTP_PORT,
+ 0 /* Unused */, AF_INET)) {
+ return mCm.createSocketKeepalive(network, tcpSocket, executor, callback);
+ } catch (Exception e) {
+ fail("Unexpected error when creating TCP socket: " + e);
+ }
+ return null;
+ });
+ }
+
+ /**
+ * Creates concurrent keepalives until the specified counts of each type of keepalives are
+ * reached or the expected error callbacks are received for each type of keepalives.
+ *
+ * @return the total number of keepalives created.
+ */
+ private int createConcurrentSocketKeepalives(
+ @NonNull Network network, int nattCount, int tcpCount) throws Exception {
+ final ArrayList<SocketKeepalive> kalist = new ArrayList<>();
+ final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
+
+ kalist.addAll(createConcurrentNattSocketKeepalives(network, nattCount, callback));
+ kalist.addAll(createConcurrentTcpSocketKeepalives(network, tcpCount, callback));
+
+ final int ret = kalist.size();
+
+ // Clean up.
+ for (final SocketKeepalive ka : kalist) {
+ ka.stop();
+ callback.expectStopped();
+ }
+ kalist.clear();
+
+ return ret;
+ }
+
+ /**
+ * Verifies that the concurrent keepalive slots meet the minimum requirement, and don't
+ * get leaked after iterations.
+ */
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ public void testSocketKeepaliveLimitWifi() throws Exception {
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+ Log.i(TAG, "testSocketKeepaliveLimitWifi cannot execute unless device"
+ + " supports WiFi");
+ return;
+ }
+
+ final Network network = ensureWifiConnected();
+ final int supported = getSupportedKeepalivesForNet(network);
+ if (supported == 0) {
+ return;
+ }
+
+ adoptShellPermissionIdentity();
+
+ // Verifies that the supported keepalive slots meet MIN_SUPPORTED_KEEPALIVE_COUNT.
+ assertGreaterOrEqual(supported, MIN_SUPPORTED_WIFI_KEEPALIVE_COUNT);
+
+ // Verifies that Nat-T keepalives can be established.
+ assertEquals(supported, createConcurrentSocketKeepalives(network, supported + 1, 0));
+ // Verifies that keepalives don't get leaked in second round.
+ assertEquals(supported, createConcurrentSocketKeepalives(network, supported, 0));
+
+ // If kernel < 4.8 then it doesn't support TCP keepalive, but it might still support
+ // NAT-T keepalive. Test below cases only if TCP keepalive is supported by kernel.
+ if (isTcpKeepaliveSupportedByKernel()) {
+ assertEquals(supported, createConcurrentSocketKeepalives(network, 0, supported + 1));
+
+ // Verifies that different types can be established at the same time.
+ assertEquals(supported, createConcurrentSocketKeepalives(network,
+ supported / 2, supported - supported / 2));
+
+ // Verifies that keepalives don't get leaked in second round.
+ assertEquals(supported, createConcurrentSocketKeepalives(network, 0, supported));
+ assertEquals(supported, createConcurrentSocketKeepalives(network,
+ supported / 2, supported - supported / 2));
+ }
+
+ dropShellPermissionIdentity();
+ }
+
+ /**
+ * Verifies that the concurrent keepalive slots meet the minimum telephony requirement, and
+ * don't get leaked after iterations.
+ */
+ @AppModeFull(reason = "Cannot request network in instant app mode")
+ public void testSocketKeepaliveLimitTelephony() throws Exception {
+ if (!mPackageManager.hasSystemFeature(FEATURE_TELEPHONY)) {
+ Log.i(TAG, "testSocketKeepaliveLimitTelephony cannot execute unless device"
+ + " supports telephony");
+ return;
+ }
+
+ final int firstSdk = Build.VERSION.FIRST_SDK_INT;
+ if (firstSdk < Build.VERSION_CODES.Q) {
+ Log.i(TAG, "testSocketKeepaliveLimitTelephony: skip test for devices launching"
+ + " before Q: " + firstSdk);
+ return;
+ }
+
+ final Network network = mCtsNetUtils.connectToCell();
+ final int supported = getSupportedKeepalivesForNet(network);
+
+ adoptShellPermissionIdentity();
+
+ // Verifies that the supported keepalive slots meet minimum requirement.
+ assertGreaterOrEqual(supported, MIN_SUPPORTED_CELLULAR_KEEPALIVE_COUNT);
+
+ // Verifies that Nat-T keepalives can be established.
+ assertEquals(supported, createConcurrentSocketKeepalives(network, supported + 1, 0));
+ // Verifies that keepalives don't get leaked in second round.
+ assertEquals(supported, createConcurrentSocketKeepalives(network, supported, 0));
+
+ dropShellPermissionIdentity();
+ }
+
+ private int getIntResourceForName(@NonNull String resName) {
+ final Resources r = mContext.getResources();
+ final int resId = r.getIdentifier(resName, "integer", "android");
+ return r.getInteger(resId);
+ }
+
+ /**
+ * Verifies that the keepalive slots are limited as customized for unprivileged requests.
+ */
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ public void testSocketKeepaliveUnprivileged() throws Exception {
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+ Log.i(TAG, "testSocketKeepaliveUnprivileged cannot execute unless device"
+ + " supports WiFi");
+ return;
+ }
+
+ final Network network = ensureWifiConnected();
+ final int supported = getSupportedKeepalivesForNet(network);
+ if (supported == 0) {
+ return;
+ }
+
+ // Resource ID might be shifted on devices that compiled with different symbols.
+ // Thus, resolve ID at runtime is needed.
+ final int allowedUnprivilegedPerUid =
+ getIntResourceForName(KEEPALIVE_ALLOWED_UNPRIVILEGED_RES_NAME);
+ final int reservedPrivilegedSlots =
+ getIntResourceForName(KEEPALIVE_RESERVED_PER_SLOT_RES_NAME);
+ // Verifies that unprivileged request per uid cannot exceed the limit customized in the
+ // resource. Currently, unprivileged keepalive slots are limited to Nat-T only, this test
+ // does not apply to TCP.
+ assertGreaterOrEqual(supported, reservedPrivilegedSlots);
+ assertGreaterOrEqual(supported, allowedUnprivilegedPerUid);
+ final int expectedUnprivileged =
+ Math.min(allowedUnprivilegedPerUid, supported - reservedPrivilegedSlots);
+ assertEquals(expectedUnprivileged,
+ createConcurrentSocketKeepalives(network, supported + 1, 0));
+ }
+
+ private static void assertGreaterOrEqual(long greater, long lesser) {
+ assertTrue("" + greater + " expected to be greater than or equal to " + lesser,
+ greater >= lesser);
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
new file mode 100644
index 0000000..ef8badd
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -0,0 +1,722 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import static android.net.DnsResolver.CLASS_IN;
+import static android.net.DnsResolver.FLAG_EMPTY;
+import static android.net.DnsResolver.FLAG_NO_CACHE_LOOKUP;
+import static android.net.DnsResolver.TYPE_A;
+import static android.net.DnsResolver.TYPE_AAAA;
+import static android.system.OsConstants.ETIMEDOUT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.ContentResolver;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.DnsPacket;
+import android.net.DnsResolver;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.ParseException;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Looper;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
+import android.system.ErrnoException;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
+public class DnsResolverTest extends AndroidTestCase {
+ private static final String TAG = "DnsResolverTest";
+ private static final char[] HEX_CHARS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+ };
+
+ static final String TEST_DOMAIN = "www.google.com";
+ static final String INVALID_PRIVATE_DNS_SERVER = "invalid.google";
+ static final byte[] TEST_BLOB = new byte[]{
+ /* Header */
+ 0x55, 0x66, /* Transaction ID */
+ 0x01, 0x00, /* Flags */
+ 0x00, 0x01, /* Questions */
+ 0x00, 0x00, /* Answer RRs */
+ 0x00, 0x00, /* Authority RRs */
+ 0x00, 0x00, /* Additional RRs */
+ /* Queries */
+ 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+ 0x00, 0x01, /* Type */
+ 0x00, 0x01 /* Class */
+ };
+ static final int TIMEOUT_MS = 12_000;
+ static final int CANCEL_TIMEOUT_MS = 3_000;
+ static final int CANCEL_RETRY_TIMES = 5;
+ static final int QUERY_TIMES = 10;
+ static final int NXDOMAIN = 3;
+ static final int PRIVATE_DNS_SETTING_TIMEOUT_MS = 2_000;
+
+ private ContentResolver mCR;
+ private ConnectivityManager mCM;
+ private Executor mExecutor;
+ private Executor mExecutorInline;
+ private DnsResolver mDns;
+
+ private String mOldMode;
+ private String mOldDnsSpecifier;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+ mDns = DnsResolver.getInstance();
+ mExecutor = new Handler(Looper.getMainLooper())::post;
+ mExecutorInline = (Runnable r) -> r.run();
+ mCR = getContext().getContentResolver();
+ storePrivateDnsSetting();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ restorePrivateDnsSetting();
+ super.tearDown();
+ }
+
+ private void storePrivateDnsSetting() {
+ // Store private DNS setting
+ mOldMode = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_MODE);
+ mOldDnsSpecifier = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_SPECIFIER);
+ }
+
+ private void restorePrivateDnsSetting() {
+ // restore private DNS setting
+ Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, mOldMode);
+ Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_SPECIFIER, mOldDnsSpecifier);
+ }
+
+ private static String byteArrayToHexString(byte[] bytes) {
+ char[] hexChars = new char[bytes.length * 2];
+ for (int i = 0; i < bytes.length; ++i) {
+ int b = bytes[i] & 0xFF;
+ hexChars[i * 2] = HEX_CHARS[b >>> 4];
+ hexChars[i * 2 + 1] = HEX_CHARS[b & 0x0F];
+ }
+ return new String(hexChars);
+ }
+
+ private Network[] getTestableNetworks() {
+ final ArrayList<Network> testableNetworks = new ArrayList<Network>();
+ for (Network network : mCM.getAllNetworks()) {
+ final NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
+ if (nc != null
+ && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+ testableNetworks.add(network);
+ }
+ }
+
+ assertTrue(
+ "This test requires that at least one network be connected. " +
+ "Please ensure that the device is connected to a network.",
+ testableNetworks.size() >= 1);
+ // In order to test query with null network, add null as an element.
+ // Test cases which query with null network will go on default network.
+ testableNetworks.add(null);
+ return testableNetworks.toArray(new Network[0]);
+ }
+
+ static private void assertGreaterThan(String msg, int first, int second) {
+ assertTrue(msg + " Excepted " + first + " to be greater than " + second, first > second);
+ }
+
+ private static class DnsParseException extends Exception {
+ public DnsParseException(String msg) {
+ super(msg);
+ }
+ }
+
+ private static class DnsAnswer extends DnsPacket {
+ DnsAnswer(@NonNull byte[] data) throws DnsParseException {
+ super(data);
+
+ // Check QR field.(query (0), or a response (1)).
+ if ((mHeader.flags & (1 << 15)) == 0) {
+ throw new DnsParseException("Not an answer packet");
+ }
+ }
+
+ int getRcode() {
+ return mHeader.rcode;
+ }
+
+ int getANCount() {
+ return mHeader.getRecordCount(ANSECTION);
+ }
+
+ int getQDCount() {
+ return mHeader.getRecordCount(QDSECTION);
+ }
+ }
+
+ /**
+ * A query callback that ensures that the query is cancelled and that onAnswer is never
+ * called. If the query succeeds before it is cancelled, needRetry will return true so the
+ * test can retry.
+ */
+ class VerifyCancelCallback implements DnsResolver.Callback<byte[]> {
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private final String mMsg;
+ private final CancellationSignal mCancelSignal;
+ private int mRcode;
+ private DnsAnswer mDnsAnswer;
+
+ VerifyCancelCallback(@NonNull String msg, @Nullable CancellationSignal cancel) {
+ mMsg = msg;
+ mCancelSignal = cancel;
+ }
+
+ VerifyCancelCallback(@NonNull String msg) {
+ this(msg, null);
+ }
+
+ public boolean waitForAnswer(int timeout) throws InterruptedException {
+ return mLatch.await(timeout, TimeUnit.MILLISECONDS);
+ }
+
+ public boolean waitForAnswer() throws InterruptedException {
+ return waitForAnswer(TIMEOUT_MS);
+ }
+
+ public boolean needRetry() throws InterruptedException {
+ return mLatch.await(CANCEL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onAnswer(@NonNull byte[] answer, int rcode) {
+ if (mCancelSignal != null && mCancelSignal.isCanceled()) {
+ fail(mMsg + " should not have returned any answers");
+ }
+
+ mRcode = rcode;
+ try {
+ mDnsAnswer = new DnsAnswer(answer);
+ } catch (ParseException | DnsParseException e) {
+ fail(mMsg + e.getMessage());
+ }
+ Log.d(TAG, "Reported blob: " + byteArrayToHexString(answer));
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onError(@NonNull DnsResolver.DnsException error) {
+ fail(mMsg + error.getMessage());
+ }
+
+ private void assertValidAnswer() {
+ assertNotNull(mMsg + " No valid answer", mDnsAnswer);
+ assertEquals(mMsg + " Unexpected error: reported rcode" + mRcode +
+ " blob's rcode " + mDnsAnswer.getRcode(), mRcode, mDnsAnswer.getRcode());
+ }
+
+ public void assertHasAnswer() {
+ assertValidAnswer();
+ // Check rcode field.(0, No error condition).
+ assertEquals(mMsg + " Response error, rcode: " + mRcode, mRcode, 0);
+ // Check answer counts.
+ assertGreaterThan(mMsg + " No answer found", mDnsAnswer.getANCount(), 0);
+ // Check question counts.
+ assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
+ }
+
+ public void assertNXDomain() {
+ assertValidAnswer();
+ // Check rcode field.(3, NXDomain).
+ assertEquals(mMsg + " Unexpected rcode: " + mRcode, mRcode, NXDOMAIN);
+ // Check answer counts. Expect 0 answer.
+ assertEquals(mMsg + " Not an empty answer", mDnsAnswer.getANCount(), 0);
+ // Check question counts.
+ assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
+ }
+
+ public void assertEmptyAnswer() {
+ assertValidAnswer();
+ // Check rcode field.(0, No error condition).
+ assertEquals(mMsg + " Response error, rcode: " + mRcode, mRcode, 0);
+ // Check answer counts. Expect 0 answer.
+ assertEquals(mMsg + " Not an empty answer", mDnsAnswer.getANCount(), 0);
+ // Check question counts.
+ assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
+ }
+ }
+
+ public void testRawQuery() throws Exception {
+ doTestRawQuery(mExecutor);
+ }
+
+ public void testRawQueryInline() throws Exception {
+ doTestRawQuery(mExecutorInline);
+ }
+
+ public void testRawQueryBlob() throws Exception {
+ doTestRawQueryBlob(mExecutor);
+ }
+
+ public void testRawQueryBlobInline() throws Exception {
+ doTestRawQueryBlob(mExecutorInline);
+ }
+
+ public void testRawQueryRoot() throws Exception {
+ doTestRawQueryRoot(mExecutor);
+ }
+
+ public void testRawQueryRootInline() throws Exception {
+ doTestRawQueryRoot(mExecutorInline);
+ }
+
+ public void testRawQueryNXDomain() throws Exception {
+ doTestRawQueryNXDomain(mExecutor);
+ }
+
+ public void testRawQueryNXDomainInline() throws Exception {
+ doTestRawQueryNXDomain(mExecutorInline);
+ }
+
+ public void doTestRawQuery(Executor executor) throws InterruptedException {
+ final String msg = "RawQuery " + TEST_DOMAIN;
+ for (Network network : getTestableNetworks()) {
+ final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
+ mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
+ executor, null, callback);
+
+ assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+ callback.waitForAnswer());
+ callback.assertHasAnswer();
+ }
+ }
+
+ public void doTestRawQueryBlob(Executor executor) throws InterruptedException {
+ final byte[] blob = new byte[]{
+ /* Header */
+ 0x55, 0x66, /* Transaction ID */
+ 0x01, 0x00, /* Flags */
+ 0x00, 0x01, /* Questions */
+ 0x00, 0x00, /* Answer RRs */
+ 0x00, 0x00, /* Authority RRs */
+ 0x00, 0x00, /* Additional RRs */
+ /* Queries */
+ 0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+ 0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+ 0x00, 0x01, /* Type */
+ 0x00, 0x01 /* Class */
+ };
+ final String msg = "RawQuery blob " + byteArrayToHexString(blob);
+ for (Network network : getTestableNetworks()) {
+ final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
+ mDns.rawQuery(network, blob, FLAG_NO_CACHE_LOOKUP, executor, null, callback);
+
+ assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+ callback.waitForAnswer());
+ callback.assertHasAnswer();
+ }
+ }
+
+ public void doTestRawQueryRoot(Executor executor) throws InterruptedException {
+ final String dname = "";
+ final String msg = "RawQuery empty dname(ROOT) ";
+ for (Network network : getTestableNetworks()) {
+ final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
+ mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
+ executor, null, callback);
+
+ assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+ callback.waitForAnswer());
+ // Except no answer record because the root does not have AAAA records.
+ callback.assertEmptyAnswer();
+ }
+ }
+
+ public void doTestRawQueryNXDomain(Executor executor) throws InterruptedException {
+ final String dname = "test1-nx.metric.gstatic.com";
+ final String msg = "RawQuery " + dname;
+ for (Network network : getTestableNetworks()) {
+ final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
+ mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
+ executor, null, callback);
+
+ assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+ callback.waitForAnswer());
+ callback.assertNXDomain();
+ }
+ }
+
+ public void testRawQueryCancel() throws InterruptedException {
+ final String msg = "Test cancel RawQuery " + TEST_DOMAIN;
+ // Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
+ // that the query is cancelled before it succeeds. If it is not cancelled before it
+ // succeeds, retry the test until it is.
+ for (Network network : getTestableNetworks()) {
+ boolean retry = false;
+ int round = 0;
+ do {
+ if (++round > CANCEL_RETRY_TIMES) {
+ fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
+ }
+ final CountDownLatch latch = new CountDownLatch(1);
+ final CancellationSignal cancelSignal = new CancellationSignal();
+ final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
+ mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
+ mExecutor, cancelSignal, callback);
+ mExecutor.execute(() -> {
+ cancelSignal.cancel();
+ latch.countDown();
+ });
+
+ retry = callback.needRetry();
+ assertTrue(msg + " query was not cancelled",
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } while (retry);
+ }
+ }
+
+ public void testRawQueryBlobCancel() throws InterruptedException {
+ final String msg = "Test cancel RawQuery blob " + byteArrayToHexString(TEST_BLOB);
+ // Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
+ // that the query is cancelled before it succeeds. If it is not cancelled before it
+ // succeeds, retry the test until it is.
+ for (Network network : getTestableNetworks()) {
+ boolean retry = false;
+ int round = 0;
+ do {
+ if (++round > CANCEL_RETRY_TIMES) {
+ fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
+ }
+ final CountDownLatch latch = new CountDownLatch(1);
+ final CancellationSignal cancelSignal = new CancellationSignal();
+ final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
+ mDns.rawQuery(network, TEST_BLOB, FLAG_EMPTY, mExecutor, cancelSignal, callback);
+ mExecutor.execute(() -> {
+ cancelSignal.cancel();
+ latch.countDown();
+ });
+
+ retry = callback.needRetry();
+ assertTrue(msg + " cancel is not done",
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } while (retry);
+ }
+ }
+
+ public void testCancelBeforeQuery() throws InterruptedException {
+ final String msg = "Test cancelled RawQuery " + TEST_DOMAIN;
+ for (Network network : getTestableNetworks()) {
+ final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
+ final CancellationSignal cancelSignal = new CancellationSignal();
+ cancelSignal.cancel();
+ mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
+ mExecutor, cancelSignal, callback);
+
+ assertTrue(msg + " should not return any answers",
+ !callback.waitForAnswer(CANCEL_TIMEOUT_MS));
+ }
+ }
+
+ /**
+ * A query callback for InetAddress that ensures that the query is
+ * cancelled and that onAnswer is never called. If the query succeeds
+ * before it is cancelled, needRetry will return true so the
+ * test can retry.
+ */
+ class VerifyCancelInetAddressCallback implements DnsResolver.Callback<List<InetAddress>> {
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private final String mMsg;
+ private final List<InetAddress> mAnswers;
+ private final CancellationSignal mCancelSignal;
+
+ VerifyCancelInetAddressCallback(@NonNull String msg, @Nullable CancellationSignal cancel) {
+ this.mMsg = msg;
+ this.mCancelSignal = cancel;
+ mAnswers = new ArrayList<>();
+ }
+
+ public boolean waitForAnswer() throws InterruptedException {
+ return mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+
+ public boolean needRetry() throws InterruptedException {
+ return mLatch.await(CANCEL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+
+ public boolean isAnswerEmpty() {
+ return mAnswers.isEmpty();
+ }
+
+ public boolean hasIpv6Answer() {
+ for (InetAddress answer : mAnswers) {
+ if (answer instanceof Inet6Address) return true;
+ }
+ return false;
+ }
+
+ public boolean hasIpv4Answer() {
+ for (InetAddress answer : mAnswers) {
+ if (answer instanceof Inet4Address) return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
+ if (mCancelSignal != null && mCancelSignal.isCanceled()) {
+ fail(mMsg + " should not have returned any answers");
+ }
+ for (InetAddress addr : answerList) {
+ Log.d(TAG, "Reported addr: " + addr.toString());
+ }
+ mAnswers.clear();
+ mAnswers.addAll(answerList);
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onError(@NonNull DnsResolver.DnsException error) {
+ fail(mMsg + error.getMessage());
+ }
+ }
+
+ public void testQueryForInetAddress() throws Exception {
+ doTestQueryForInetAddress(mExecutor);
+ }
+
+ public void testQueryForInetAddressInline() throws Exception {
+ doTestQueryForInetAddress(mExecutorInline);
+ }
+
+ public void testQueryForInetAddressIpv4() throws Exception {
+ doTestQueryForInetAddressIpv4(mExecutor);
+ }
+
+ public void testQueryForInetAddressIpv4Inline() throws Exception {
+ doTestQueryForInetAddressIpv4(mExecutorInline);
+ }
+
+ public void testQueryForInetAddressIpv6() throws Exception {
+ doTestQueryForInetAddressIpv6(mExecutor);
+ }
+
+ public void testQueryForInetAddressIpv6Inline() throws Exception {
+ doTestQueryForInetAddressIpv6(mExecutorInline);
+ }
+
+ public void testContinuousQueries() throws Exception {
+ doTestContinuousQueries(mExecutor);
+ }
+
+ public void testContinuousQueriesInline() throws Exception {
+ doTestContinuousQueries(mExecutorInline);
+ }
+
+ public void doTestQueryForInetAddress(Executor executor) throws InterruptedException {
+ final String msg = "Test query for InetAddress " + TEST_DOMAIN;
+ for (Network network : getTestableNetworks()) {
+ final VerifyCancelInetAddressCallback callback =
+ new VerifyCancelInetAddressCallback(msg, null);
+ mDns.query(network, TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, executor, null, callback);
+
+ assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+ callback.waitForAnswer());
+ assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
+ }
+ }
+
+ public void testQueryCancelForInetAddress() throws InterruptedException {
+ final String msg = "Test cancel query for InetAddress " + TEST_DOMAIN;
+ // Start a DNS query and the cancel it immediately. Use VerifyCancelInetAddressCallback to
+ // expect that the query is cancelled before it succeeds. If it is not cancelled before it
+ // succeeds, retry the test until it is.
+ for (Network network : getTestableNetworks()) {
+ boolean retry = false;
+ int round = 0;
+ do {
+ if (++round > CANCEL_RETRY_TIMES) {
+ fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
+ }
+ final CountDownLatch latch = new CountDownLatch(1);
+ final CancellationSignal cancelSignal = new CancellationSignal();
+ final VerifyCancelInetAddressCallback callback =
+ new VerifyCancelInetAddressCallback(msg, cancelSignal);
+ mDns.query(network, TEST_DOMAIN, FLAG_EMPTY, mExecutor, cancelSignal, callback);
+ mExecutor.execute(() -> {
+ cancelSignal.cancel();
+ latch.countDown();
+ });
+
+ retry = callback.needRetry();
+ assertTrue(msg + " query was not cancelled",
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } while (retry);
+ }
+ }
+
+ public void doTestQueryForInetAddressIpv4(Executor executor) throws InterruptedException {
+ final String msg = "Test query for IPv4 InetAddress " + TEST_DOMAIN;
+ for (Network network : getTestableNetworks()) {
+ final VerifyCancelInetAddressCallback callback =
+ new VerifyCancelInetAddressCallback(msg, null);
+ mDns.query(network, TEST_DOMAIN, TYPE_A, FLAG_NO_CACHE_LOOKUP,
+ executor, null, callback);
+
+ assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+ callback.waitForAnswer());
+ assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
+ assertTrue(msg + " returned Ipv6 results", !callback.hasIpv6Answer());
+ }
+ }
+
+ public void doTestQueryForInetAddressIpv6(Executor executor) throws InterruptedException {
+ final String msg = "Test query for IPv6 InetAddress " + TEST_DOMAIN;
+ for (Network network : getTestableNetworks()) {
+ final VerifyCancelInetAddressCallback callback =
+ new VerifyCancelInetAddressCallback(msg, null);
+ mDns.query(network, TEST_DOMAIN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
+ executor, null, callback);
+
+ assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+ callback.waitForAnswer());
+ assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
+ assertTrue(msg + " returned Ipv4 results", !callback.hasIpv4Answer());
+ }
+ }
+
+ private void awaitPrivateDnsSetting(@NonNull String msg,
+ @NonNull Network network, @NonNull String server) throws InterruptedException {
+ CountDownLatch latch = new CountDownLatch(1);
+ NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
+ NetworkCallback callback = new NetworkCallback() {
+ @Override
+ public void onLinkPropertiesChanged(Network n, LinkProperties lp) {
+ if (network.equals(n) && server.equals(lp.getPrivateDnsServerName())) {
+ latch.countDown();
+ }
+ }
+ };
+ mCM.registerNetworkCallback(request, callback);
+ assertTrue(msg, latch.await(PRIVATE_DNS_SETTING_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ mCM.unregisterNetworkCallback(callback);
+ }
+
+ public void testPrivateDnsBypass() throws InterruptedException {
+ final Network[] testNetworks = getTestableNetworks();
+
+ // Set an invalid private DNS server
+ Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, "hostname");
+ Settings.Global.putString(mCR,
+ Settings.Global.PRIVATE_DNS_SPECIFIER, INVALID_PRIVATE_DNS_SERVER);
+
+ final String msg = "Test PrivateDnsBypass " + TEST_DOMAIN;
+ for (Network network : testNetworks) {
+ // This test cannot be ran with null network because we need to explicitly pass a
+ // private DNS bypassable network or bind one.
+ if (network == null) continue;
+
+ // wait for private DNS setting propagating
+ awaitPrivateDnsSetting(msg + " wait private DNS setting timeout",
+ network, INVALID_PRIVATE_DNS_SERVER);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ final DnsResolver.Callback<List<InetAddress>> errorCallback =
+ new DnsResolver.Callback<List<InetAddress>>() {
+ @Override
+ public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
+ fail(msg + " should not get valid answer");
+ }
+
+ @Override
+ public void onError(@NonNull DnsResolver.DnsException error) {
+ assertEquals(DnsResolver.ERROR_SYSTEM, error.code);
+ assertEquals(ETIMEDOUT, ((ErrnoException) error.getCause()).errno);
+ latch.countDown();
+ }
+ };
+ // Private DNS strict mode with invalid DNS server is set
+ // Expect no valid answer returned but ErrnoException with ETIMEDOUT
+ mDns.query(network, TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, errorCallback);
+
+ assertTrue(msg + " invalid server round. No response after " + TIMEOUT_MS + "ms.",
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ final VerifyCancelInetAddressCallback callback =
+ new VerifyCancelInetAddressCallback(msg, null);
+ // Bypass privateDns, expect query works fine
+ mDns.query(network.getPrivateDnsBypassingCopy(),
+ TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callback);
+
+ assertTrue(msg + " bypass private DNS round. No answer after " + TIMEOUT_MS + "ms.",
+ callback.waitForAnswer());
+ assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
+
+ // To ensure private DNS bypass still work even if passing null network.
+ // Bind process network with a private DNS bypassable network.
+ mCM.bindProcessToNetwork(network.getPrivateDnsBypassingCopy());
+ final VerifyCancelInetAddressCallback callbackWithNullNetwork =
+ new VerifyCancelInetAddressCallback(msg + " with null network ", null);
+ mDns.query(null,
+ TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callbackWithNullNetwork);
+
+ assertTrue(msg + " with null network bypass private DNS round. No answer after " +
+ TIMEOUT_MS + "ms.", callbackWithNullNetwork.waitForAnswer());
+ assertTrue(msg + " with null network returned 0 results",
+ !callbackWithNullNetwork.isAnswerEmpty());
+
+ // Reset process network to default.
+ mCM.bindProcessToNetwork(null);
+ }
+ }
+
+ public void doTestContinuousQueries(Executor executor) throws InterruptedException {
+ final String msg = "Test continuous " + QUERY_TIMES + " queries " + TEST_DOMAIN;
+ for (Network network : getTestableNetworks()) {
+ for (int i = 0; i < QUERY_TIMES ; ++i) {
+ final VerifyCancelInetAddressCallback callback =
+ new VerifyCancelInetAddressCallback(msg, null);
+ // query v6/v4 in turn
+ boolean queryV6 = (i % 2 == 0);
+ mDns.query(network, TEST_DOMAIN, queryV6 ? TYPE_AAAA : TYPE_A,
+ FLAG_NO_CACHE_LOOKUP, executor, null, callback);
+
+ assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+ callback.waitForAnswer());
+ assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
+ assertTrue(msg + " returned " + (queryV6 ? "Ipv4" : "Ipv6") + " results",
+ queryV6 ? !callback.hasIpv4Answer() : !callback.hasIpv6Answer());
+ }
+ }
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/DnsTest.java b/tests/cts/net/src/android/net/cts/DnsTest.java
index 84231c2..746dcb0 100644
--- a/tests/cts/net/src/android/net/cts/DnsTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsTest.java
@@ -287,7 +287,7 @@
final NetworkCallback callback = new NetworkCallback() {
@Override
public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
- if (lp.hasGlobalIPv6Address()) {
+ if (lp.hasGlobalIpv6Address()) {
latch.countDown();
}
}
diff --git a/tests/cts/net/src/android/net/cts/InetAddressesTest.java b/tests/cts/net/src/android/net/cts/InetAddressesTest.java
new file mode 100644
index 0000000..7837ce9
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/InetAddressesTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.cts;
+
+import android.net.InetAddresses;
+import java.net.InetAddress;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(JUnitParamsRunner.class)
+public class InetAddressesTest {
+
+ public static String[][] validNumericAddressesAndStringRepresentation() {
+ return new String[][] {
+ // Regular IPv4.
+ { "1.2.3.4", "1.2.3.4" },
+
+ // Regular IPv6.
+ { "2001:4860:800d::68", "2001:4860:800d::68" },
+ { "1234:5678::9ABC:DEF0", "1234:5678::9abc:def0" },
+ { "2001:cdba:9abc:5678::", "2001:cdba:9abc:5678::" },
+ { "::2001:cdba:9abc:5678", "::2001:cdba:9abc:5678" },
+ { "64:ff9b::1.2.3.4", "64:ff9b::102:304" },
+
+ { "::9abc:5678", "::154.188.86.120" },
+
+ // Mapped IPv4
+ { "::ffff:127.0.0.1", "127.0.0.1" },
+
+ // Android does not recognize Octal (leading 0) cases: they are treated as decimal.
+ { "0177.00.00.01", "177.0.0.1" },
+
+ // Verify that examples from JavaDoc work correctly.
+ { "192.0.2.1", "192.0.2.1" },
+ { "2001:db8::1:2", "2001:db8::1:2" },
+ };
+ }
+
+ public static String[] invalidNumericAddresses() {
+ return new String[] {
+ "",
+ " ",
+ "\t",
+ "\n",
+ "1.2.3.4.",
+ "1.2.3",
+ "1.2",
+ "1",
+ "1234",
+ "0",
+ "0x1.0x2.0x3.0x4",
+ "0x7f.0x00.0x00.0x01",
+ "0256.00.00.01",
+ "fred",
+ "www.google.com",
+ // IPv6 encoded for use in URL as defined in RFC 2732
+ "[fe80::6:2222]",
+ };
+ }
+
+ @Parameters(method = "validNumericAddressesAndStringRepresentation")
+ @Test
+ public void parseNumericAddress(String address, String expectedString) {
+ InetAddress inetAddress = InetAddresses.parseNumericAddress(address);
+ assertEquals(expectedString, inetAddress.getHostAddress());
+ }
+
+ @Parameters(method = "invalidNumericAddresses")
+ @Test
+ public void test_parseNonNumericAddress(String address) {
+ try {
+ InetAddress inetAddress = InetAddresses.parseNumericAddress(address);
+ fail(String.format(
+ "Address %s is not numeric but was parsed as %s", address, inetAddress));
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage()).contains(address);
+ }
+ }
+
+ @Test
+ public void test_parseNumericAddress_null() {
+ try {
+ InetAddress inetAddress = InetAddresses.parseNumericAddress(null);
+ fail(String.format("null is not numeric but was parsed as %s", inetAddress));
+ } catch (NullPointerException e) {
+ // expected
+ }
+ }
+
+ @Parameters(method = "validNumericAddressesAndStringRepresentation")
+ @Test
+ public void test_isNumericAddress(String address, String unused) {
+ assertTrue("expected '" + address + "' to be treated as numeric",
+ InetAddresses.isNumericAddress(address));
+ }
+
+ @Parameters(method = "invalidNumericAddresses")
+ @Test
+ public void test_isNotNumericAddress(String address) {
+ assertFalse("expected '" + address + "' to be treated as non-numeric",
+ InetAddresses.isNumericAddress(address));
+ }
+
+ @Test
+ public void test_isNumericAddress_null() {
+ try {
+ InetAddresses.isNumericAddress(null);
+ fail("expected null to throw a NullPointerException");
+ } catch (NullPointerException e) {
+ // expected
+ }
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
index 7132ecf..10e43e7 100644
--- a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
@@ -19,19 +19,22 @@
import static org.junit.Assert.assertArrayEquals;
import android.content.Context;
+import android.net.ConnectivityManager;
import android.net.IpSecAlgorithm;
import android.net.IpSecManager;
import android.net.IpSecTransform;
+import android.platform.test.annotations.AppModeFull;
import android.system.Os;
import android.system.OsConstants;
-import android.test.AndroidTestCase;
import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
-import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -41,7 +44,12 @@
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
-public class IpSecBaseTest extends AndroidTestCase {
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class IpSecBaseTest {
private static final String TAG = IpSecBaseTest.class.getSimpleName();
@@ -66,11 +74,19 @@
protected static final byte[] AUTH_KEY = getKey(256);
protected static final byte[] CRYPT_KEY = getKey(256);
+ protected ConnectivityManager mCM;
protected IpSecManager mISM;
- protected void setUp() throws Exception {
- super.setUp();
- mISM = (IpSecManager) getContext().getSystemService(Context.IPSEC_SERVICE);
+ @Before
+ public void setUp() throws Exception {
+ mISM =
+ (IpSecManager)
+ InstrumentationRegistry.getContext()
+ .getSystemService(Context.IPSEC_SERVICE);
+ mCM =
+ (ConnectivityManager)
+ InstrumentationRegistry.getContext()
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
}
protected static byte[] getKey(int bitLength) {
@@ -192,6 +208,17 @@
public static class JavaUdpSocket implements GenericUdpSocket {
public final DatagramSocket mSocket;
+ public JavaUdpSocket(InetAddress localAddr, int port) {
+ try {
+ mSocket = new DatagramSocket(port, localAddr);
+ mSocket.setSoTimeout(SOCK_TIMEOUT);
+ } catch (SocketException e) {
+ // Fail loudly if we can't set up sockets properly. And without the timeout, we
+ // could easily end up in an endless wait.
+ throw new RuntimeException(e);
+ }
+ }
+
public JavaUdpSocket(InetAddress localAddr) {
try {
mSocket = new DatagramSocket(0, localAddr);
@@ -422,35 +449,36 @@
}
protected static IpSecTransform buildIpSecTransform(
- Context mContext,
+ Context context,
IpSecManager.SecurityParameterIndex spi,
IpSecManager.UdpEncapsulationSocket encapSocket,
InetAddress remoteAddr)
throws Exception {
- String localAddr = (remoteAddr instanceof Inet4Address) ? IPV4_LOOPBACK : IPV6_LOOPBACK;
IpSecTransform.Builder builder =
- new IpSecTransform.Builder(mContext)
- .setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
- .setAuthentication(
- new IpSecAlgorithm(
- IpSecAlgorithm.AUTH_HMAC_SHA256,
- AUTH_KEY,
- AUTH_KEY.length * 4));
+ new IpSecTransform.Builder(context)
+ .setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
+ .setAuthentication(
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_HMAC_SHA256,
+ AUTH_KEY,
+ AUTH_KEY.length * 4));
if (encapSocket != null) {
builder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
}
- return builder.buildTransportModeTransform(InetAddress.getByName(localAddr), spi);
+ return builder.buildTransportModeTransform(remoteAddr, spi);
}
private IpSecTransform buildDefaultTransform(InetAddress localAddr) throws Exception {
try (IpSecManager.SecurityParameterIndex spi =
mISM.allocateSecurityParameterIndex(localAddr)) {
- return buildIpSecTransform(mContext, spi, null, localAddr);
+ return buildIpSecTransform(InstrumentationRegistry.getContext(), spi, null, localAddr);
}
}
+ @Test
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testJavaTcpSocketPair() throws Exception {
for (String addr : LOOPBACK_ADDRS) {
InetAddress local = InetAddress.getByName(addr);
@@ -461,6 +489,8 @@
}
}
+ @Test
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testJavaUdpSocketPair() throws Exception {
for (String addr : LOOPBACK_ADDRS) {
InetAddress local = InetAddress.getByName(addr);
@@ -472,6 +502,8 @@
}
}
+ @Test
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testJavaUdpSocketPairUnconnected() throws Exception {
for (String addr : LOOPBACK_ADDRS) {
InetAddress local = InetAddress.getByName(addr);
@@ -483,6 +515,8 @@
}
}
+ @Test
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testNativeTcpSocketPair() throws Exception {
for (String addr : LOOPBACK_ADDRS) {
InetAddress local = InetAddress.getByName(addr);
@@ -494,6 +528,8 @@
}
}
+ @Test
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testNativeUdpSocketPair() throws Exception {
for (String addr : LOOPBACK_ADDRS) {
InetAddress local = InetAddress.getByName(addr);
@@ -505,6 +541,8 @@
}
}
+ @Test
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testNativeUdpSocketPairUnconnected() throws Exception {
for (String addr : LOOPBACK_ADDRS) {
InetAddress local = InetAddress.getByName(addr);
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index a18b2f0..355b496 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -16,46 +16,55 @@
package android.net.cts;
+import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE;
+import static android.net.cts.PacketUtils.AES_CBC_IV_LEN;
+import static android.net.cts.PacketUtils.AES_GCM_BLK_SIZE;
+import static android.net.cts.PacketUtils.AES_GCM_IV_LEN;
+import static android.net.cts.PacketUtils.IP4_HDRLEN;
+import static android.net.cts.PacketUtils.IP6_HDRLEN;
+import static android.net.cts.PacketUtils.TCP_HDRLEN_WITH_TIMESTAMP_OPT;
+import static android.net.cts.PacketUtils.UDP_HDRLEN;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertTrue;
-import android.content.Context;
-import android.net.ConnectivityManager;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.net.IpSecAlgorithm;
import android.net.IpSecManager;
import android.net.IpSecTransform;
import android.net.TrafficStats;
+import android.platform.test.annotations.AppModeFull;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet6Address;
import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Socket cannot bind in instant app mode")
public class IpSecManagerTest extends IpSecBaseTest {
private static final String TAG = IpSecManagerTest.class.getSimpleName();
- private ConnectivityManager mCM;
-
- private static InetAddress IpAddress(String addrString) {
- try {
- return InetAddress.getByName(addrString);
- } catch (UnknownHostException e) {
- throw new IllegalArgumentException("Invalid IP address: " + e);
- }
- }
-
- private static final InetAddress GOOGLE_DNS_4 = IpAddress("8.8.8.8");
- private static final InetAddress GOOGLE_DNS_6 = IpAddress("2001:4860:4860::8888");
+ private static final InetAddress GOOGLE_DNS_4 = InetAddress.parseNumericAddress("8.8.8.8");
+ private static final InetAddress GOOGLE_DNS_6 =
+ InetAddress.parseNumericAddress("2001:4860:4860::8888");
private static final InetAddress[] GOOGLE_DNS_LIST =
new InetAddress[] {GOOGLE_DNS_4, GOOGLE_DNS_6};
@@ -65,28 +74,13 @@
private static final byte[] AEAD_KEY = getKey(288);
- private static final int TCP_HDRLEN_WITH_OPTIONS = 32;
- private static final int UDP_HDRLEN = 8;
- private static final int IP4_HDRLEN = 20;
- private static final int IP6_HDRLEN = 40;
-
- // Encryption parameters
- private static final int AES_GCM_IV_LEN = 8;
- private static final int AES_CBC_IV_LEN = 16;
- private static final int AES_GCM_BLK_SIZE = 4;
- private static final int AES_CBC_BLK_SIZE = 16;
-
- protected void setUp() throws Exception {
- super.setUp();
- mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
- }
-
/*
* Allocate a random SPI
* Allocate a specific SPI using previous randomly created SPI value
* Realloc the same SPI that was specifically created (expect SpiUnavailable)
* Close SPIs
*/
+ @Test
public void testAllocSpi() throws Exception {
for (InetAddress addr : GOOGLE_DNS_LIST) {
IpSecManager.SecurityParameterIndex randomSpi = null, droidSpi = null;
@@ -238,13 +232,14 @@
* release transform
* send data (expect exception)
*/
+ @Test
public void testCreateTransform() throws Exception {
InetAddress localAddr = InetAddress.getByName(IPV4_LOOPBACK);
IpSecManager.SecurityParameterIndex spi =
mISM.allocateSecurityParameterIndex(localAddr);
IpSecTransform transform =
- new IpSecTransform.Builder(mContext)
+ new IpSecTransform.Builder(InstrumentationRegistry.getContext())
.setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
.setAuthentication(
new IpSecAlgorithm(
@@ -445,19 +440,6 @@
}
}
- /** Helper function to calculate expected ESP packet size. */
- private int calculateEspPacketSize(
- int payloadLen, int cryptIvLength, int cryptBlockSize, int authTruncLen) {
- final int ESP_HDRLEN = 4 + 4; // SPI + Seq#
- final int ICV_LEN = authTruncLen / 8; // Auth trailer; based on truncation length
- payloadLen += cryptIvLength; // Initialization Vector
- payloadLen += 2; // ESP trailer
-
- // Align to block size of encryption algorithm
- payloadLen += (cryptBlockSize - (payloadLen % cryptBlockSize)) % cryptBlockSize;
- return payloadLen + ESP_HDRLEN + ICV_LEN;
- }
-
public void checkTransform(
int protocol,
String localAddress,
@@ -475,7 +457,8 @@
IpSecManager.SecurityParameterIndex spi =
mISM.allocateSecurityParameterIndex(local)) {
- IpSecTransform.Builder transformBuilder = new IpSecTransform.Builder(mContext);
+ IpSecTransform.Builder transformBuilder =
+ new IpSecTransform.Builder(InstrumentationRegistry.getContext());
if (crypt != null) {
transformBuilder.setEncryption(crypt);
}
@@ -498,7 +481,7 @@
try (IpSecTransform transform =
transformBuilder.buildTransportModeTransform(local, spi)) {
if (protocol == IPPROTO_TCP) {
- transportHdrLen = TCP_HDRLEN_WITH_OPTIONS;
+ transportHdrLen = TCP_HDRLEN_WITH_TIMESTAMP_OPT;
checkTcp(transform, local, sendCount, useJavaSockets);
} else if (protocol == IPPROTO_UDP) {
transportHdrLen = UDP_HDRLEN;
@@ -535,7 +518,7 @@
int innerPacketSize = TEST_DATA.length + transportHdrLen + ipHdrLen;
int outerPacketSize =
- calculateEspPacketSize(
+ PacketUtils.calculateEspPacketSize(
TEST_DATA.length + transportHdrLen, ivLen, blkSize, truncLenBits)
+ udpEncapLen
+ ipHdrLen;
@@ -553,13 +536,13 @@
// Add TCP ACKs for data packets
if (protocol == IPPROTO_TCP) {
int encryptedTcpPktSize =
- calculateEspPacketSize(TCP_HDRLEN_WITH_OPTIONS, ivLen, blkSize, truncLenBits);
+ PacketUtils.calculateEspPacketSize(
+ TCP_HDRLEN_WITH_TIMESTAMP_OPT, ivLen, blkSize, truncLenBits);
-
- // Add data packet ACKs
- expectedOuterBytes += (encryptedTcpPktSize + udpEncapLen + ipHdrLen) * (sendCount);
- expectedInnerBytes += (TCP_HDRLEN_WITH_OPTIONS + ipHdrLen) * (sendCount);
- expectedPackets += sendCount;
+ // Add data packet ACKs
+ expectedOuterBytes += (encryptedTcpPktSize + udpEncapLen + ipHdrLen) * (sendCount);
+ expectedInnerBytes += (TCP_HDRLEN_WITH_TIMESTAMP_OPT + ipHdrLen) * (sendCount);
+ expectedPackets += sendCount;
}
StatsChecker.waitForNumPackets(expectedPackets);
@@ -620,6 +603,7 @@
}
}
+ @Test
public void testIkeOverUdpEncapSocket() throws Exception {
// IPv6 not supported for UDP-encap-ESP
InetAddress local = InetAddress.getByName(IPV4_LOOPBACK);
@@ -635,7 +619,7 @@
try (IpSecManager.SecurityParameterIndex spi =
mISM.allocateSecurityParameterIndex(local);
IpSecTransform transform =
- new IpSecTransform.Builder(mContext)
+ new IpSecTransform.Builder(InstrumentationRegistry.getContext())
.setEncryption(crypt)
.setAuthentication(auth)
.setIpv4Encapsulation(encapSocket, encapSocket.getPort())
@@ -678,24 +662,28 @@
// checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, true, 1000);
// }
+ @Test
public void testInterfaceCountersUdp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1000, false);
}
+ @Test
public void testInterfaceCountersUdp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1000, false);
}
+ @Test
public void testInterfaceCountersUdp4UdpEncap() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1000, false);
}
+ @Test
public void testAesCbcHmacMd5Tcp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
@@ -703,6 +691,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacMd5Tcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
@@ -710,6 +699,7 @@
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacMd5Udp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
@@ -717,6 +707,7 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacMd5Udp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
@@ -724,6 +715,7 @@
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha1Tcp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
@@ -731,6 +723,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha1Tcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
@@ -738,6 +731,7 @@
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha1Udp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
@@ -745,6 +739,7 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha1Udp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
@@ -752,6 +747,7 @@
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha256Tcp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
@@ -759,6 +755,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha256Tcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
@@ -766,6 +763,7 @@
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha256Udp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
@@ -773,6 +771,7 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha256Udp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
@@ -780,6 +779,7 @@
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha384Tcp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
@@ -787,6 +787,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha384Tcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
@@ -794,6 +795,7 @@
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha384Udp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
@@ -801,6 +803,7 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha384Udp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
@@ -808,6 +811,7 @@
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha512Tcp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
@@ -815,6 +819,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha512Tcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
@@ -822,6 +827,7 @@
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha512Udp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
@@ -829,6 +835,7 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesCbcHmacSha512Udp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
@@ -836,6 +843,7 @@
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, auth, null, false, 1, true);
}
+ @Test
public void testAesGcm64Tcp4() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
@@ -843,6 +851,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
}
+ @Test
public void testAesGcm64Tcp6() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
@@ -850,6 +859,7 @@
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
}
+ @Test
public void testAesGcm64Udp4() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
@@ -857,6 +867,7 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
}
+ @Test
public void testAesGcm64Udp6() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
@@ -864,6 +875,7 @@
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
}
+ @Test
public void testAesGcm96Tcp4() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
@@ -871,6 +883,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
}
+ @Test
public void testAesGcm96Tcp6() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
@@ -878,6 +891,7 @@
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
}
+ @Test
public void testAesGcm96Udp4() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
@@ -885,6 +899,7 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
}
+ @Test
public void testAesGcm96Udp6() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
@@ -892,6 +907,7 @@
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
}
+ @Test
public void testAesGcm128Tcp4() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
@@ -899,6 +915,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
}
+ @Test
public void testAesGcm128Tcp6() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
@@ -906,6 +923,7 @@
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
}
+ @Test
public void testAesGcm128Udp4() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
@@ -913,6 +931,7 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, false, 1, true);
}
+ @Test
public void testAesGcm128Udp6() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
@@ -920,6 +939,7 @@
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, null, authCrypt, false, 1, true);
}
+ @Test
public void testAesCbcHmacMd5Tcp4UdpEncap() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
@@ -927,6 +947,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
}
+ @Test
public void testAesCbcHmacMd5Udp4UdpEncap() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
@@ -934,6 +955,7 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
}
+ @Test
public void testAesCbcHmacSha1Tcp4UdpEncap() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
@@ -941,6 +963,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
}
+ @Test
public void testAesCbcHmacSha1Udp4UdpEncap() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA1, getKey(160), 96);
@@ -948,6 +971,7 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
}
+ @Test
public void testAesCbcHmacSha256Tcp4UdpEncap() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
@@ -955,6 +979,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
}
+ @Test
public void testAesCbcHmacSha256Udp4UdpEncap() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
@@ -962,6 +987,7 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
}
+ @Test
public void testAesCbcHmacSha384Tcp4UdpEncap() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
@@ -969,6 +995,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
}
+ @Test
public void testAesCbcHmacSha384Udp4UdpEncap() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA384, getKey(384), 192);
@@ -976,6 +1003,7 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
}
+ @Test
public void testAesCbcHmacSha512Tcp4UdpEncap() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
@@ -983,6 +1011,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
}
+ @Test
public void testAesCbcHmacSha512Udp4UdpEncap() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA512, getKey(512), 256);
@@ -990,6 +1019,7 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, auth, null, true, 1, true);
}
+ @Test
public void testAesGcm64Tcp4UdpEncap() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
@@ -997,6 +1027,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
}
+ @Test
public void testAesGcm64Udp4UdpEncap() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 64);
@@ -1004,6 +1035,7 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
}
+ @Test
public void testAesGcm96Tcp4UdpEncap() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
@@ -1011,6 +1043,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
}
+ @Test
public void testAesGcm96Udp4UdpEncap() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 96);
@@ -1018,6 +1051,7 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
}
+ @Test
public void testAesGcm128Tcp4UdpEncap() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
@@ -1025,6 +1059,7 @@
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
}
+ @Test
public void testAesGcm128Udp4UdpEncap() throws Exception {
IpSecAlgorithm authCrypt =
new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128);
@@ -1032,78 +1067,91 @@
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, null, authCrypt, true, 1, true);
}
+ @Test
public void testCryptUdp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, null, false, 1, false);
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, null, false, 1, true);
}
+ @Test
public void testAuthUdp4() throws Exception {
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, null, false, 1, false);
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, null, false, 1, true);
}
+ @Test
public void testCryptUdp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, null, null, false, 1, false);
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, crypt, null, null, false, 1, true);
}
+ @Test
public void testAuthUdp6() throws Exception {
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, auth, null, false, 1, false);
checkTransform(IPPROTO_UDP, IPV6_LOOPBACK, null, auth, null, false, 1, true);
}
+ @Test
public void testCryptTcp4() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, null, false, 1, false);
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, null, false, 1, true);
}
+ @Test
public void testAuthTcp4() throws Exception {
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, null, false, 1, false);
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, null, false, 1, true);
}
+ @Test
public void testCryptTcp6() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, null, null, false, 1, false);
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, crypt, null, null, false, 1, true);
}
+ @Test
public void testAuthTcp6() throws Exception {
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, auth, null, false, 1, false);
checkTransform(IPPROTO_TCP, IPV6_LOOPBACK, null, auth, null, false, 1, true);
}
+ @Test
public void testCryptUdp4UdpEncap() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, null, true, 1, false);
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, crypt, null, null, true, 1, true);
}
+ @Test
public void testAuthUdp4UdpEncap() throws Exception {
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, null, true, 1, false);
checkTransform(IPPROTO_UDP, IPV4_LOOPBACK, null, auth, null, true, 1, true);
}
+ @Test
public void testCryptTcp4UdpEncap() throws Exception {
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, null, true, 1, false);
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, crypt, null, null, true, 1, true);
}
+ @Test
public void testAuthTcp4UdpEncap() throws Exception {
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, getKey(256), 128);
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, null, true, 1, false);
checkTransform(IPPROTO_TCP, IPV4_LOOPBACK, null, auth, null, true, 1, true);
}
+ @Test
public void testOpenUdpEncapSocketSpecificPort() throws Exception {
IpSecManager.UdpEncapsulationSocket encapSocket = null;
int port = -1;
@@ -1132,6 +1180,7 @@
assertTrue("Returned invalid port", encapSocket.getPort() == port);
}
+ @Test
public void testOpenUdpEncapSocketRandomPort() throws Exception {
try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
assertTrue("Returned invalid port", encapSocket.getPort() != 0);
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
new file mode 100644
index 0000000..999d2f1
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
@@ -0,0 +1,953 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
+import static android.net.IpSecManager.UdpEncapsulationSocket;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE;
+import static android.net.cts.PacketUtils.AES_CBC_IV_LEN;
+import static android.net.cts.PacketUtils.BytePayload;
+import static android.net.cts.PacketUtils.EspHeader;
+import static android.net.cts.PacketUtils.IP4_HDRLEN;
+import static android.net.cts.PacketUtils.IP6_HDRLEN;
+import static android.net.cts.PacketUtils.Ip4Header;
+import static android.net.cts.PacketUtils.Ip6Header;
+import static android.net.cts.PacketUtils.IpHeader;
+import static android.net.cts.PacketUtils.UDP_HDRLEN;
+import static android.net.cts.PacketUtils.UdpHeader;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.IpSecAlgorithm;
+import android.net.IpSecManager;
+import android.net.IpSecTransform;
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.TestNetworkInterface;
+import android.net.TestNetworkManager;
+import android.net.cts.PacketUtils.Payload;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemProperties;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
+public class IpSecManagerTunnelTest extends IpSecBaseTest {
+ private static final String TAG = IpSecManagerTunnelTest.class.getSimpleName();
+
+ private static final InetAddress LOCAL_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.1");
+ private static final InetAddress REMOTE_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.2");
+ private static final InetAddress LOCAL_OUTER_6 =
+ InetAddress.parseNumericAddress("2001:db8:1::1");
+ private static final InetAddress REMOTE_OUTER_6 =
+ InetAddress.parseNumericAddress("2001:db8:1::2");
+
+ private static final InetAddress LOCAL_INNER_4 =
+ InetAddress.parseNumericAddress("198.51.100.1");
+ private static final InetAddress REMOTE_INNER_4 =
+ InetAddress.parseNumericAddress("198.51.100.2");
+ private static final InetAddress LOCAL_INNER_6 =
+ InetAddress.parseNumericAddress("2001:db8:2::1");
+ private static final InetAddress REMOTE_INNER_6 =
+ InetAddress.parseNumericAddress("2001:db8:2::2");
+
+ private static final int IP4_PREFIX_LEN = 32;
+ private static final int IP6_PREFIX_LEN = 128;
+
+ private static final int TIMEOUT_MS = 500;
+
+ // Static state to reduce setup/teardown
+ private static ConnectivityManager sCM;
+ private static TestNetworkManager sTNM;
+ private static ParcelFileDescriptor sTunFd;
+ private static TestNetworkCallback sTunNetworkCallback;
+ private static Network sTunNetwork;
+ private static TunUtils sTunUtils;
+
+ private static Context sContext = InstrumentationRegistry.getContext();
+ private static IBinder sBinder = new Binder();
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity();
+ sCM = (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ sTNM = (TestNetworkManager) sContext.getSystemService(Context.TEST_NETWORK_SERVICE);
+
+ // Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted, and
+ // a standard permission is insufficient. So we shell out the appop, to give us the
+ // right appop permissions.
+ setAppop(OP_MANAGE_IPSEC_TUNNELS, true);
+
+ TestNetworkInterface testIface =
+ sTNM.createTunInterface(
+ new LinkAddress[] {
+ new LinkAddress(LOCAL_OUTER_4, IP4_PREFIX_LEN),
+ new LinkAddress(LOCAL_OUTER_6, IP6_PREFIX_LEN)
+ });
+
+ sTunFd = testIface.getFileDescriptor();
+ sTunNetworkCallback = setupAndGetTestNetwork(testIface.getInterfaceName());
+ sTunNetwork = sTunNetworkCallback.getNetworkBlocking();
+
+ sTunUtils = new TunUtils(sTunFd);
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // Set to true before every run; some tests flip this.
+ setAppop(OP_MANAGE_IPSEC_TUNNELS, true);
+
+ // Clear sTunUtils state
+ sTunUtils.reset();
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ setAppop(OP_MANAGE_IPSEC_TUNNELS, false);
+
+ sCM.unregisterNetworkCallback(sTunNetworkCallback);
+
+ sTNM.teardownTestNetwork(sTunNetwork);
+ sTunFd.close();
+
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+
+ private static boolean hasTunnelsFeature() {
+ return sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
+ || SystemProperties.getInt("ro.product.first_api_level", 0)
+ >= Build.VERSION_CODES.Q;
+ }
+
+ private static void setAppop(int appop, boolean allow) {
+ String opName = AppOpsManager.opToName(appop);
+ for (String pkg : new String[] {"com.android.shell", sContext.getPackageName()}) {
+ String cmd =
+ String.format(
+ "appops set %s %s %s",
+ pkg, // Package name
+ opName, // Appop
+ (allow ? "allow" : "deny")); // Action
+ SystemUtil.runShellCommand(cmd);
+ }
+ }
+
+ private static TestNetworkCallback setupAndGetTestNetwork(String ifname) throws Exception {
+ // Build a network request
+ NetworkRequest nr =
+ new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .removeCapability(NET_CAPABILITY_NOT_VPN)
+ .setNetworkSpecifier(ifname)
+ .build();
+
+ TestNetworkCallback cb = new TestNetworkCallback();
+ sCM.requestNetwork(nr, cb);
+
+ // Setup the test network after network request is filed to prevent Network from being
+ // reaped due to no requests matching it.
+ sTNM.setupTestNetwork(ifname, sBinder);
+
+ return cb;
+ }
+
+ @Test
+ public void testSecurityExceptionCreateTunnelInterfaceWithoutAppop() throws Exception {
+ if (!hasTunnelsFeature()) return;
+
+ // Ensure we don't have the appop. Permission is not requested in the Manifest
+ setAppop(OP_MANAGE_IPSEC_TUNNELS, false);
+
+ // Security exceptions are thrown regardless of IPv4/IPv6. Just test one
+ try {
+ mISM.createIpSecTunnelInterface(LOCAL_INNER_6, REMOTE_INNER_6, sTunNetwork);
+ fail("Did not throw SecurityException for Tunnel creation without appop");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
+ public void testSecurityExceptionBuildTunnelTransformWithoutAppop() throws Exception {
+ if (!hasTunnelsFeature()) return;
+
+ // Ensure we don't have the appop. Permission is not requested in the Manifest
+ setAppop(OP_MANAGE_IPSEC_TUNNELS, false);
+
+ // Security exceptions are thrown regardless of IPv4/IPv6. Just test one
+ try (IpSecManager.SecurityParameterIndex spi =
+ mISM.allocateSecurityParameterIndex(LOCAL_INNER_4);
+ IpSecTransform transform =
+ new IpSecTransform.Builder(sContext)
+ .buildTunnelModeTransform(REMOTE_INNER_4, spi)) {
+ fail("Did not throw SecurityException for Transform creation without appop");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ /* Test runnables for callbacks after IPsec tunnels are set up. */
+ private abstract class IpSecTunnelTestRunnable {
+ /**
+ * Runs the test code, and returns the inner socket port, if any.
+ *
+ * @param ipsecNetwork The IPsec Interface based Network for binding sockets on
+ * @return the integer port of the inner socket if outbound, or 0 if inbound
+ * IpSecTunnelTestRunnable
+ * @throws Exception if any part of the test failed.
+ */
+ public abstract int run(Network ipsecNetwork) throws Exception;
+ }
+
+ private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+ private final CompletableFuture<Network> futureNetwork = new CompletableFuture<>();
+
+ @Override
+ public void onAvailable(Network network) {
+ futureNetwork.complete(network);
+ }
+
+ public Network getNetworkBlocking() throws Exception {
+ return futureNetwork.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ private int getPacketSize(
+ int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) {
+ int expectedPacketSize = TEST_DATA.length + UDP_HDRLEN;
+
+ // Inner Transport mode packet size
+ if (transportInTunnelMode) {
+ expectedPacketSize =
+ PacketUtils.calculateEspPacketSize(
+ expectedPacketSize,
+ AES_CBC_IV_LEN,
+ AES_CBC_BLK_SIZE,
+ AUTH_KEY.length * 4);
+ }
+
+ // Inner IP Header
+ expectedPacketSize += innerFamily == AF_INET ? IP4_HDRLEN : IP6_HDRLEN;
+
+ // Tunnel mode transform size
+ expectedPacketSize =
+ PacketUtils.calculateEspPacketSize(
+ expectedPacketSize, AES_CBC_IV_LEN, AES_CBC_BLK_SIZE, AUTH_KEY.length * 4);
+
+ // UDP encap size
+ expectedPacketSize += useEncap ? UDP_HDRLEN : 0;
+
+ // Outer IP Header
+ expectedPacketSize += outerFamily == AF_INET ? IP4_HDRLEN : IP6_HDRLEN;
+
+ return expectedPacketSize;
+ }
+
+ private interface IpSecTunnelTestRunnableFactory {
+ IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
+ boolean transportInTunnelMode,
+ int spi,
+ InetAddress localInner,
+ InetAddress remoteInner,
+ InetAddress localOuter,
+ InetAddress remoteOuter,
+ IpSecTransform inTransportTransform,
+ IpSecTransform outTransportTransform,
+ int encapPort,
+ int innerSocketPort,
+ int expectedPacketSize)
+ throws Exception;
+ }
+
+ private class OutputIpSecTunnelTestRunnableFactory implements IpSecTunnelTestRunnableFactory {
+ public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
+ boolean transportInTunnelMode,
+ int spi,
+ InetAddress localInner,
+ InetAddress remoteInner,
+ InetAddress localOuter,
+ InetAddress remoteOuter,
+ IpSecTransform inTransportTransform,
+ IpSecTransform outTransportTransform,
+ int encapPort,
+ int unusedInnerSocketPort,
+ int expectedPacketSize) {
+ return new IpSecTunnelTestRunnable() {
+ @Override
+ public int run(Network ipsecNetwork) throws Exception {
+ // Build a socket and send traffic
+ JavaUdpSocket socket = new JavaUdpSocket(localInner);
+ ipsecNetwork.bindSocket(socket.mSocket);
+ int innerSocketPort = socket.getPort();
+
+ // For Transport-In-Tunnel mode, apply transform to socket
+ if (transportInTunnelMode) {
+ mISM.applyTransportModeTransform(
+ socket.mSocket, IpSecManager.DIRECTION_IN, inTransportTransform);
+ mISM.applyTransportModeTransform(
+ socket.mSocket, IpSecManager.DIRECTION_OUT, outTransportTransform);
+ }
+
+ socket.sendTo(TEST_DATA, remoteInner, socket.getPort());
+
+ // Verify that an encrypted packet is sent. As of right now, checking encrypted
+ // body is not possible, due to the test not knowing some of the fields of the
+ // inner IP header (flow label, flags, etc)
+ sTunUtils.awaitEspPacketNoPlaintext(
+ spi, TEST_DATA, encapPort != 0, expectedPacketSize);
+
+ socket.close();
+
+ return innerSocketPort;
+ }
+ };
+ }
+ }
+
+ private class InputReflectedIpSecTunnelTestRunnableFactory
+ implements IpSecTunnelTestRunnableFactory {
+ public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
+ boolean transportInTunnelMode,
+ int spi,
+ InetAddress localInner,
+ InetAddress remoteInner,
+ InetAddress localOuter,
+ InetAddress remoteOuter,
+ IpSecTransform inTransportTransform,
+ IpSecTransform outTransportTransform,
+ int encapPort,
+ int innerSocketPort,
+ int expectedPacketSize)
+ throws Exception {
+ return new IpSecTunnelTestRunnable() {
+ @Override
+ public int run(Network ipsecNetwork) throws Exception {
+ // Build a socket and receive traffic
+ JavaUdpSocket socket = new JavaUdpSocket(localInner, innerSocketPort);
+ ipsecNetwork.bindSocket(socket.mSocket);
+
+ // For Transport-In-Tunnel mode, apply transform to socket
+ if (transportInTunnelMode) {
+ mISM.applyTransportModeTransform(
+ socket.mSocket, IpSecManager.DIRECTION_IN, outTransportTransform);
+ mISM.applyTransportModeTransform(
+ socket.mSocket, IpSecManager.DIRECTION_OUT, inTransportTransform);
+ }
+
+ sTunUtils.reflectPackets();
+
+ // Receive packet from socket, and validate that the payload is correct
+ receiveAndValidatePacket(socket);
+
+ socket.close();
+
+ return 0;
+ }
+ };
+ }
+ }
+
+ private class InputPacketGeneratorIpSecTunnelTestRunnableFactory
+ implements IpSecTunnelTestRunnableFactory {
+ public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
+ boolean transportInTunnelMode,
+ int spi,
+ InetAddress localInner,
+ InetAddress remoteInner,
+ InetAddress localOuter,
+ InetAddress remoteOuter,
+ IpSecTransform inTransportTransform,
+ IpSecTransform outTransportTransform,
+ int encapPort,
+ int innerSocketPort,
+ int expectedPacketSize)
+ throws Exception {
+ return new IpSecTunnelTestRunnable() {
+ @Override
+ public int run(Network ipsecNetwork) throws Exception {
+ // Build a socket and receive traffic
+ JavaUdpSocket socket = new JavaUdpSocket(localInner);
+ ipsecNetwork.bindSocket(socket.mSocket);
+
+ // For Transport-In-Tunnel mode, apply transform to socket
+ if (transportInTunnelMode) {
+ mISM.applyTransportModeTransform(
+ socket.mSocket, IpSecManager.DIRECTION_IN, outTransportTransform);
+ mISM.applyTransportModeTransform(
+ socket.mSocket, IpSecManager.DIRECTION_OUT, inTransportTransform);
+ }
+
+ byte[] pkt;
+ if (transportInTunnelMode) {
+ pkt =
+ getTransportInTunnelModePacket(
+ spi,
+ spi,
+ remoteInner,
+ localInner,
+ remoteOuter,
+ localOuter,
+ socket.getPort(),
+ encapPort);
+ } else {
+ pkt =
+ getTunnelModePacket(
+ spi,
+ remoteInner,
+ localInner,
+ remoteOuter,
+ localOuter,
+ socket.getPort(),
+ encapPort);
+ }
+ sTunUtils.injectPacket(pkt);
+
+ // Receive packet from socket, and validate
+ receiveAndValidatePacket(socket);
+
+ socket.close();
+
+ return 0;
+ }
+ };
+ }
+ }
+
+ private void checkTunnelOutput(
+ int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
+ throws Exception {
+ checkTunnel(
+ innerFamily,
+ outerFamily,
+ useEncap,
+ transportInTunnelMode,
+ new OutputIpSecTunnelTestRunnableFactory());
+ }
+
+ private void checkTunnelInput(
+ int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
+ throws Exception {
+ checkTunnel(
+ innerFamily,
+ outerFamily,
+ useEncap,
+ transportInTunnelMode,
+ new InputPacketGeneratorIpSecTunnelTestRunnableFactory());
+ }
+
+ /**
+ * Validates that the kernel can talk to itself.
+ *
+ * <p>This test takes an outbound IPsec packet, reflects it (by flipping IP src/dst), and
+ * injects it back into the TUN. This test then verifies that a packet with the correct payload
+ * is found on the specified socket/port.
+ */
+ public void checkTunnelReflected(
+ int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
+ throws Exception {
+ if (!hasTunnelsFeature()) return;
+
+ InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6;
+ InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6;
+
+ InetAddress localOuter = outerFamily == AF_INET ? LOCAL_OUTER_4 : LOCAL_OUTER_6;
+ InetAddress remoteOuter = outerFamily == AF_INET ? REMOTE_OUTER_4 : REMOTE_OUTER_6;
+
+ // Preselect both SPI and encap port, to be used for both inbound and outbound tunnels.
+ int spi = getRandomSpi(localOuter, remoteOuter);
+ int expectedPacketSize =
+ getPacketSize(innerFamily, outerFamily, useEncap, transportInTunnelMode);
+
+ try (IpSecManager.SecurityParameterIndex inTransportSpi =
+ mISM.allocateSecurityParameterIndex(localInner, spi);
+ IpSecManager.SecurityParameterIndex outTransportSpi =
+ mISM.allocateSecurityParameterIndex(remoteInner, spi);
+ IpSecTransform inTransportTransform =
+ buildIpSecTransform(sContext, inTransportSpi, null, remoteInner);
+ IpSecTransform outTransportTransform =
+ buildIpSecTransform(sContext, outTransportSpi, null, localInner);
+ UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
+
+ // Run output direction tests
+ IpSecTunnelTestRunnable outputIpSecTunnelTestRunnable =
+ new OutputIpSecTunnelTestRunnableFactory()
+ .getIpSecTunnelTestRunnable(
+ transportInTunnelMode,
+ spi,
+ localInner,
+ remoteInner,
+ localOuter,
+ remoteOuter,
+ inTransportTransform,
+ outTransportTransform,
+ useEncap ? encapSocket.getPort() : 0,
+ 0,
+ expectedPacketSize);
+ int innerSocketPort =
+ buildTunnelNetworkAndRunTests(
+ localInner,
+ remoteInner,
+ localOuter,
+ remoteOuter,
+ spi,
+ useEncap ? encapSocket : null,
+ outputIpSecTunnelTestRunnable);
+
+ // Input direction tests, with matching inner socket ports.
+ IpSecTunnelTestRunnable inputIpSecTunnelTestRunnable =
+ new InputReflectedIpSecTunnelTestRunnableFactory()
+ .getIpSecTunnelTestRunnable(
+ transportInTunnelMode,
+ spi,
+ remoteInner,
+ localInner,
+ localOuter,
+ remoteOuter,
+ inTransportTransform,
+ outTransportTransform,
+ useEncap ? encapSocket.getPort() : 0,
+ innerSocketPort,
+ expectedPacketSize);
+ buildTunnelNetworkAndRunTests(
+ remoteInner,
+ localInner,
+ localOuter,
+ remoteOuter,
+ spi,
+ useEncap ? encapSocket : null,
+ inputIpSecTunnelTestRunnable);
+ }
+ }
+
+ public void checkTunnel(
+ int innerFamily,
+ int outerFamily,
+ boolean useEncap,
+ boolean transportInTunnelMode,
+ IpSecTunnelTestRunnableFactory factory)
+ throws Exception {
+ if (!hasTunnelsFeature()) return;
+
+ InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6;
+ InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6;
+
+ InetAddress localOuter = outerFamily == AF_INET ? LOCAL_OUTER_4 : LOCAL_OUTER_6;
+ InetAddress remoteOuter = outerFamily == AF_INET ? REMOTE_OUTER_4 : REMOTE_OUTER_6;
+
+ // Preselect both SPI and encap port, to be used for both inbound and outbound tunnels.
+ // Re-uses the same SPI to ensure that even in cases of symmetric SPIs shared across tunnel
+ // and transport mode, packets are encrypted/decrypted properly based on the src/dst.
+ int spi = getRandomSpi(localOuter, remoteOuter);
+ int expectedPacketSize =
+ getPacketSize(innerFamily, outerFamily, useEncap, transportInTunnelMode);
+
+ try (IpSecManager.SecurityParameterIndex inTransportSpi =
+ mISM.allocateSecurityParameterIndex(localInner, spi);
+ IpSecManager.SecurityParameterIndex outTransportSpi =
+ mISM.allocateSecurityParameterIndex(remoteInner, spi);
+ IpSecTransform inTransportTransform =
+ buildIpSecTransform(sContext, inTransportSpi, null, remoteInner);
+ IpSecTransform outTransportTransform =
+ buildIpSecTransform(sContext, outTransportSpi, null, localInner);
+ UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
+
+ buildTunnelNetworkAndRunTests(
+ localInner,
+ remoteInner,
+ localOuter,
+ remoteOuter,
+ spi,
+ useEncap ? encapSocket : null,
+ factory.getIpSecTunnelTestRunnable(
+ transportInTunnelMode,
+ spi,
+ localInner,
+ remoteInner,
+ localOuter,
+ remoteOuter,
+ inTransportTransform,
+ outTransportTransform,
+ useEncap ? encapSocket.getPort() : 0,
+ 0,
+ expectedPacketSize));
+ }
+ }
+
+ private int buildTunnelNetworkAndRunTests(
+ InetAddress localInner,
+ InetAddress remoteInner,
+ InetAddress localOuter,
+ InetAddress remoteOuter,
+ int spi,
+ UdpEncapsulationSocket encapSocket,
+ IpSecTunnelTestRunnable test)
+ throws Exception {
+ int innerPrefixLen = localInner instanceof Inet6Address ? IP6_PREFIX_LEN : IP4_PREFIX_LEN;
+ TestNetworkCallback testNetworkCb = null;
+ int innerSocketPort;
+
+ try (IpSecManager.SecurityParameterIndex inSpi =
+ mISM.allocateSecurityParameterIndex(localOuter, spi);
+ IpSecManager.SecurityParameterIndex outSpi =
+ mISM.allocateSecurityParameterIndex(remoteOuter, spi);
+ IpSecManager.IpSecTunnelInterface tunnelIface =
+ mISM.createIpSecTunnelInterface(localOuter, remoteOuter, sTunNetwork)) {
+ // Build the test network
+ tunnelIface.addAddress(localInner, innerPrefixLen);
+ testNetworkCb = setupAndGetTestNetwork(tunnelIface.getInterfaceName());
+ Network testNetwork = testNetworkCb.getNetworkBlocking();
+
+ // Check interface was created
+ assertNotNull(NetworkInterface.getByName(tunnelIface.getInterfaceName()));
+
+ // Verify address was added
+ final NetworkInterface netIface = NetworkInterface.getByInetAddress(localInner);
+ assertNotNull(netIface);
+ assertEquals(tunnelIface.getInterfaceName(), netIface.getDisplayName());
+
+ // Configure Transform parameters
+ IpSecTransform.Builder transformBuilder = new IpSecTransform.Builder(sContext);
+ transformBuilder.setEncryption(
+ new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY));
+ transformBuilder.setAuthentication(
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4));
+
+ if (encapSocket != null) {
+ transformBuilder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
+ }
+
+ // Apply transform and check that traffic is properly encrypted
+ try (IpSecTransform inTransform =
+ transformBuilder.buildTunnelModeTransform(remoteOuter, inSpi);
+ IpSecTransform outTransform =
+ transformBuilder.buildTunnelModeTransform(localOuter, outSpi)) {
+ mISM.applyTunnelModeTransform(tunnelIface, IpSecManager.DIRECTION_IN, inTransform);
+ mISM.applyTunnelModeTransform(
+ tunnelIface, IpSecManager.DIRECTION_OUT, outTransform);
+
+ innerSocketPort = test.run(testNetwork);
+ }
+
+ // Teardown the test network
+ sTNM.teardownTestNetwork(testNetwork);
+
+ // Remove addresses and check that interface is still present, but fails lookup-by-addr
+ tunnelIface.removeAddress(localInner, innerPrefixLen);
+ assertNotNull(NetworkInterface.getByName(tunnelIface.getInterfaceName()));
+ assertNull(NetworkInterface.getByInetAddress(localInner));
+
+ // Check interface was cleaned up
+ tunnelIface.close();
+ assertNull(NetworkInterface.getByName(tunnelIface.getInterfaceName()));
+ } finally {
+ if (testNetworkCb != null) {
+ sCM.unregisterNetworkCallback(testNetworkCb);
+ }
+ }
+
+ return innerSocketPort;
+ }
+
+ private static void receiveAndValidatePacket(JavaUdpSocket socket) throws Exception {
+ byte[] socketResponseBytes = socket.receive();
+ assertArrayEquals(TEST_DATA, socketResponseBytes);
+ }
+
+ private int getRandomSpi(InetAddress localOuter, InetAddress remoteOuter) throws Exception {
+ // Try to allocate both in and out SPIs using the same requested SPI value.
+ try (IpSecManager.SecurityParameterIndex inSpi =
+ mISM.allocateSecurityParameterIndex(localOuter);
+ IpSecManager.SecurityParameterIndex outSpi =
+ mISM.allocateSecurityParameterIndex(remoteOuter, inSpi.getSpi()); ) {
+ return inSpi.getSpi();
+ }
+ }
+
+ private IpHeader getIpHeader(int protocol, InetAddress src, InetAddress dst, Payload payload) {
+ if ((src instanceof Inet6Address) != (dst instanceof Inet6Address)) {
+ throw new IllegalArgumentException("Invalid src/dst address combination");
+ }
+
+ if (src instanceof Inet6Address) {
+ return new Ip6Header(protocol, (Inet6Address) src, (Inet6Address) dst, payload);
+ } else {
+ return new Ip4Header(protocol, (Inet4Address) src, (Inet4Address) dst, payload);
+ }
+ }
+
+ private EspHeader buildTransportModeEspPacket(
+ int spi, InetAddress src, InetAddress dst, int port, Payload payload) throws Exception {
+ IpHeader preEspIpHeader = getIpHeader(payload.getProtocolId(), src, dst, payload);
+
+ return new EspHeader(
+ payload.getProtocolId(),
+ spi,
+ 1, // sequence number
+ CRYPT_KEY, // Same key for auth and crypt
+ payload.getPacketBytes(preEspIpHeader));
+ }
+
+ private EspHeader buildTunnelModeEspPacket(
+ int spi,
+ InetAddress srcInner,
+ InetAddress dstInner,
+ InetAddress srcOuter,
+ InetAddress dstOuter,
+ int port,
+ int encapPort,
+ Payload payload)
+ throws Exception {
+ IpHeader innerIp = getIpHeader(payload.getProtocolId(), srcInner, dstInner, payload);
+ return new EspHeader(
+ innerIp.getProtocolId(),
+ spi,
+ 1, // sequence number
+ CRYPT_KEY, // Same key for auth and crypt
+ innerIp.getPacketBytes());
+ }
+
+ private IpHeader maybeEncapPacket(
+ InetAddress src, InetAddress dst, int encapPort, EspHeader espPayload)
+ throws Exception {
+
+ Payload payload = espPayload;
+ if (encapPort != 0) {
+ payload = new UdpHeader(encapPort, encapPort, espPayload);
+ }
+
+ return getIpHeader(payload.getProtocolId(), src, dst, payload);
+ }
+
+ private byte[] getTunnelModePacket(
+ int spi,
+ InetAddress srcInner,
+ InetAddress dstInner,
+ InetAddress srcOuter,
+ InetAddress dstOuter,
+ int port,
+ int encapPort)
+ throws Exception {
+ UdpHeader udp = new UdpHeader(port, port, new BytePayload(TEST_DATA));
+
+ EspHeader espPayload =
+ buildTunnelModeEspPacket(
+ spi, srcInner, dstInner, srcOuter, dstOuter, port, encapPort, udp);
+ return maybeEncapPacket(srcOuter, dstOuter, encapPort, espPayload).getPacketBytes();
+ }
+
+ private byte[] getTransportInTunnelModePacket(
+ int spiInner,
+ int spiOuter,
+ InetAddress srcInner,
+ InetAddress dstInner,
+ InetAddress srcOuter,
+ InetAddress dstOuter,
+ int port,
+ int encapPort)
+ throws Exception {
+ UdpHeader udp = new UdpHeader(port, port, new BytePayload(TEST_DATA));
+
+ EspHeader espPayload = buildTransportModeEspPacket(spiInner, srcInner, dstInner, port, udp);
+ espPayload =
+ buildTunnelModeEspPacket(
+ spiOuter,
+ srcInner,
+ dstInner,
+ srcOuter,
+ dstOuter,
+ port,
+ encapPort,
+ espPayload);
+ return maybeEncapPacket(srcOuter, dstOuter, encapPort, espPayload).getPacketBytes();
+ }
+
+ // Transport-in-Tunnel mode tests
+ @Test
+ public void testTransportInTunnelModeV4InV4() throws Exception {
+ checkTunnelOutput(AF_INET, AF_INET, false, true);
+ checkTunnelInput(AF_INET, AF_INET, false, true);
+ }
+
+ @Test
+ public void testTransportInTunnelModeV4InV4Reflected() throws Exception {
+ checkTunnelReflected(AF_INET, AF_INET, false, true);
+ }
+
+ @Test
+ public void testTransportInTunnelModeV4InV4UdpEncap() throws Exception {
+ checkTunnelOutput(AF_INET, AF_INET, true, true);
+ checkTunnelInput(AF_INET, AF_INET, true, true);
+ }
+
+ @Test
+ public void testTransportInTunnelModeV4InV4UdpEncapReflected() throws Exception {
+ checkTunnelReflected(AF_INET, AF_INET, false, true);
+ }
+
+ @Test
+ public void testTransportInTunnelModeV4InV6() throws Exception {
+ checkTunnelOutput(AF_INET, AF_INET6, false, true);
+ checkTunnelInput(AF_INET, AF_INET6, false, true);
+ }
+
+ @Test
+ public void testTransportInTunnelModeV4InV6Reflected() throws Exception {
+ checkTunnelReflected(AF_INET, AF_INET, false, true);
+ }
+
+ @Test
+ public void testTransportInTunnelModeV6InV4() throws Exception {
+ checkTunnelOutput(AF_INET6, AF_INET, false, true);
+ checkTunnelInput(AF_INET6, AF_INET, false, true);
+ }
+
+ @Test
+ public void testTransportInTunnelModeV6InV4Reflected() throws Exception {
+ checkTunnelReflected(AF_INET, AF_INET, false, true);
+ }
+
+ @Test
+ public void testTransportInTunnelModeV6InV4UdpEncap() throws Exception {
+ checkTunnelOutput(AF_INET6, AF_INET, true, true);
+ checkTunnelInput(AF_INET6, AF_INET, true, true);
+ }
+
+ @Test
+ public void testTransportInTunnelModeV6InV4UdpEncapReflected() throws Exception {
+ checkTunnelReflected(AF_INET, AF_INET, false, true);
+ }
+
+ @Test
+ public void testTransportInTunnelModeV6InV6() throws Exception {
+ checkTunnelOutput(AF_INET, AF_INET6, false, true);
+ checkTunnelInput(AF_INET, AF_INET6, false, true);
+ }
+
+ @Test
+ public void testTransportInTunnelModeV6InV6Reflected() throws Exception {
+ checkTunnelReflected(AF_INET, AF_INET, false, true);
+ }
+
+ // Tunnel mode tests
+ @Test
+ public void testTunnelV4InV4() throws Exception {
+ checkTunnelOutput(AF_INET, AF_INET, false, false);
+ checkTunnelInput(AF_INET, AF_INET, false, false);
+ }
+
+ @Test
+ public void testTunnelV4InV4Reflected() throws Exception {
+ checkTunnelReflected(AF_INET, AF_INET, false, false);
+ }
+
+ @Test
+ public void testTunnelV4InV4UdpEncap() throws Exception {
+ checkTunnelOutput(AF_INET, AF_INET, true, false);
+ checkTunnelInput(AF_INET, AF_INET, true, false);
+ }
+
+ @Test
+ public void testTunnelV4InV4UdpEncapReflected() throws Exception {
+ checkTunnelReflected(AF_INET, AF_INET, true, false);
+ }
+
+ @Test
+ public void testTunnelV4InV6() throws Exception {
+ checkTunnelOutput(AF_INET, AF_INET6, false, false);
+ checkTunnelInput(AF_INET, AF_INET6, false, false);
+ }
+
+ @Test
+ public void testTunnelV4InV6Reflected() throws Exception {
+ checkTunnelReflected(AF_INET, AF_INET6, false, false);
+ }
+
+ @Test
+ public void testTunnelV6InV4() throws Exception {
+ checkTunnelOutput(AF_INET6, AF_INET, false, false);
+ checkTunnelInput(AF_INET6, AF_INET, false, false);
+ }
+
+ @Test
+ public void testTunnelV6InV4Reflected() throws Exception {
+ checkTunnelReflected(AF_INET6, AF_INET, false, false);
+ }
+
+ @Test
+ public void testTunnelV6InV4UdpEncap() throws Exception {
+ checkTunnelOutput(AF_INET6, AF_INET, true, false);
+ checkTunnelInput(AF_INET6, AF_INET, true, false);
+ }
+
+ @Test
+ public void testTunnelV6InV4UdpEncapReflected() throws Exception {
+ checkTunnelReflected(AF_INET6, AF_INET, true, false);
+ }
+
+ @Test
+ public void testTunnelV6InV6() throws Exception {
+ checkTunnelOutput(AF_INET6, AF_INET6, false, false);
+ checkTunnelInput(AF_INET6, AF_INET6, false, false);
+ }
+
+ @Test
+ public void testTunnelV6InV6Reflected() throws Exception {
+ checkTunnelReflected(AF_INET6, AF_INET6, false, false);
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/IpSecSysctlTest.java b/tests/cts/net/src/android/net/cts/IpSecSysctlTest.java
deleted file mode 100644
index b362282..0000000
--- a/tests/cts/net/src/android/net/cts/IpSecSysctlTest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.cts;
-
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-import android.system.StructStat;
-import android.test.AndroidTestCase;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-/**
- * Tests for multinetwork sysctl functionality.
- */
-public class IpSecSysctlTest extends SysctlBaseTest {
-
- // SPI expiration sysctls. Must be present and set greater than 1h.
- private static final String SPI_TIMEOUT_SYSCTL = "/proc/sys/net/core/xfrm_acq_expires";
- private static final int MIN_ACQ_EXPIRES = 3600;
-
- /**
- * Checks that SPI default timeouts are overridden, and set to a reasonable length of time
- */
- public void testProcFiles() throws ErrnoException, IOException, NumberFormatException {
- int value = getIntValue(SPI_TIMEOUT_SYSCTL);
- assertAtLeast(SPI_TIMEOUT_SYSCTL, value, MIN_ACQ_EXPIRES);
- }
-}
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index b2c9d9b..c3e65b7 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -43,6 +43,12 @@
private static native int runSetprocnetwork(long networkHandle);
private static native int runSetsocknetwork(long networkHandle);
private static native int runDatagramCheck(long networkHandle);
+ private static native int runResNapiMalformedCheck(long networkHandle);
+ private static native int runResNcancelCheck(long networkHandle);
+ private static native int runResNqueryCheck(long networkHandle);
+ private static native int runResNsendCheck(long networkHandle);
+
+
private ConnectivityManager mCM;
@@ -175,4 +181,14 @@
fail();
} catch (IllegalArgumentException e) {}
}
+
+ public void testResNApi() {
+ for (Network network : getTestableNetworks()) {
+ // Throws AssertionError directly in jni function if test fail.
+ runResNqueryCheck(network.getNetworkHandle());
+ runResNsendCheck(network.getNetworkHandle());
+ runResNcancelCheck(network.getNetworkHandle());
+ runResNapiMalformedCheck(network.getNetworkHandle());
+ }
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkSysctlTest.java b/tests/cts/net/src/android/net/cts/MultinetworkSysctlTest.java
deleted file mode 100644
index 1d0c111..0000000
--- a/tests/cts/net/src/android/net/cts/MultinetworkSysctlTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.cts;
-
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-import android.system.StructStat;
-import android.test.AndroidTestCase;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-/**
- * Tests for multinetwork sysctl functionality.
- */
-public class MultinetworkSysctlTest extends SysctlBaseTest {
-
- // Global sysctls. Must be present and set to 1.
- private static final String[] GLOBAL_SYSCTLS = {
- "/proc/sys/net/ipv4/fwmark_reflect",
- "/proc/sys/net/ipv6/fwmark_reflect",
- "/proc/sys/net/ipv4/tcp_fwmark_accept",
- };
-
- // Per-interface IPv6 autoconf sysctls.
- private static final String IPV6_SYSCTL_DIR = "/proc/sys/net/ipv6/conf";
- private static final String AUTOCONF_SYSCTL = "accept_ra_rt_table";
-
- /**
- * Checks that the sysctls for multinetwork kernel features are present and
- * enabled. The necessary kernel commits are:
- *
- * Mainline Linux:
- * e110861 net: add a sysctl to reflect the fwmark on replies
- * 1b3c61d net: Use fwmark reflection in PMTU discovery.
- * 84f39b0 net: support marking accepting TCP sockets
- *
- * Common Android tree (e.g., 3.10):
- * a03f539 net: ipv6: autoconf routes into per-device tables
- */
- public void testProcFiles() throws ErrnoException, IOException, NumberFormatException {
- for (String sysctl : GLOBAL_SYSCTLS) {
- int value = getIntValue(sysctl);
- assertEquals(sysctl, 1, value);
- }
-
- File[] interfaceDirs = new File(IPV6_SYSCTL_DIR).listFiles();
- for (File interfaceDir : interfaceDirs) {
- if (interfaceDir.getName().equals("all") || interfaceDir.getName().equals("lo")) {
- continue;
- }
- String sysctl = new File(interfaceDir, AUTOCONF_SYSCTL).getAbsolutePath();
- int value = getIntValue(sysctl);
- assertLess(sysctl, value, 0);
- }
- }
-}
diff --git a/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java b/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java
index 8ad7820..e4e350c 100644
--- a/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.net.ConnectivityManager;
+import android.platform.test.annotations.AppModeFull;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
@@ -97,6 +98,7 @@
* returns the hash of config we set.
*/
@Test
+ @AppModeFull(reason = "Cannot access resource file in instant app mode")
public void testGetWatchlistConfigHash() throws Exception {
// Set watchlist config file for test
setWatchlistConfig(TEST_WATCHLIST_XML);
diff --git a/tests/cts/net/src/android/net/cts/PacketUtils.java b/tests/cts/net/src/android/net/cts/PacketUtils.java
new file mode 100644
index 0000000..6177827
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/PacketUtils.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import static android.system.OsConstants.IPPROTO_IPV6;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class PacketUtils {
+ private static final String TAG = PacketUtils.class.getSimpleName();
+
+ private static final int DATA_BUFFER_LEN = 4096;
+
+ static final int IP4_HDRLEN = 20;
+ static final int IP6_HDRLEN = 40;
+ static final int UDP_HDRLEN = 8;
+ static final int TCP_HDRLEN = 20;
+ static final int TCP_HDRLEN_WITH_TIMESTAMP_OPT = TCP_HDRLEN + 12;
+
+ // Not defined in OsConstants
+ static final int IPPROTO_IPV4 = 4;
+ static final int IPPROTO_ESP = 50;
+
+ // Encryption parameters
+ static final int AES_GCM_IV_LEN = 8;
+ static final int AES_CBC_IV_LEN = 16;
+ static final int AES_GCM_BLK_SIZE = 4;
+ static final int AES_CBC_BLK_SIZE = 16;
+
+ // Encryption algorithms
+ static final String AES = "AES";
+ static final String AES_CBC = "AES/CBC/NoPadding";
+ static final String HMAC_SHA_256 = "HmacSHA256";
+
+ public interface Payload {
+ byte[] getPacketBytes(IpHeader header) throws Exception;
+
+ void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) throws Exception;
+
+ short length();
+
+ int getProtocolId();
+ }
+
+ public abstract static class IpHeader {
+
+ public final byte proto;
+ public final InetAddress srcAddr;
+ public final InetAddress dstAddr;
+ public final Payload payload;
+
+ public IpHeader(int proto, InetAddress src, InetAddress dst, Payload payload) {
+ this.proto = (byte) proto;
+ this.srcAddr = src;
+ this.dstAddr = dst;
+ this.payload = payload;
+ }
+
+ public abstract byte[] getPacketBytes() throws Exception;
+
+ public abstract int getProtocolId();
+ }
+
+ public static class Ip4Header extends IpHeader {
+ private short checksum;
+
+ public Ip4Header(int proto, Inet4Address src, Inet4Address dst, Payload payload) {
+ super(proto, src, dst, payload);
+ }
+
+ public byte[] getPacketBytes() throws Exception {
+ ByteBuffer resultBuffer = buildHeader();
+ payload.addPacketBytes(this, resultBuffer);
+
+ return getByteArrayFromBuffer(resultBuffer);
+ }
+
+ public ByteBuffer buildHeader() {
+ ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN);
+
+ // Version, IHL
+ bb.put((byte) (0x45));
+
+ // DCSP, ECN
+ bb.put((byte) 0);
+
+ // Total Length
+ bb.putShort((short) (IP4_HDRLEN + payload.length()));
+
+ // Empty for Identification, Flags and Fragment Offset
+ bb.putShort((short) 0);
+ bb.put((byte) 0x40);
+ bb.put((byte) 0x00);
+
+ // TTL
+ bb.put((byte) 64);
+
+ // Protocol
+ bb.put(proto);
+
+ // Header Checksum
+ final int ipChecksumOffset = bb.position();
+ bb.putShort((short) 0);
+
+ // Src/Dst addresses
+ bb.put(srcAddr.getAddress());
+ bb.put(dstAddr.getAddress());
+
+ bb.putShort(ipChecksumOffset, calculateChecksum(bb));
+
+ return bb;
+ }
+
+ private short calculateChecksum(ByteBuffer bb) {
+ int checksum = 0;
+
+ // Calculate sum of 16-bit values, excluding checksum. IPv4 headers are always 32-bit
+ // aligned, so no special cases needed for unaligned values.
+ ShortBuffer shortBuffer = ByteBuffer.wrap(getByteArrayFromBuffer(bb)).asShortBuffer();
+ while (shortBuffer.hasRemaining()) {
+ short val = shortBuffer.get();
+
+ // Wrap as needed
+ checksum = addAndWrapForChecksum(checksum, val);
+ }
+
+ return onesComplement(checksum);
+ }
+
+ public int getProtocolId() {
+ return IPPROTO_IPV4;
+ }
+ }
+
+ public static class Ip6Header extends IpHeader {
+ public Ip6Header(int nextHeader, Inet6Address src, Inet6Address dst, Payload payload) {
+ super(nextHeader, src, dst, payload);
+ }
+
+ public byte[] getPacketBytes() throws Exception {
+ ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN);
+
+ // Version | Traffic Class (First 4 bits)
+ bb.put((byte) 0x60);
+
+ // Traffic class (Last 4 bits), Flow Label
+ bb.put((byte) 0);
+ bb.put((byte) 0);
+ bb.put((byte) 0);
+
+ // Payload Length
+ bb.putShort((short) payload.length());
+
+ // Next Header
+ bb.put(proto);
+
+ // Hop Limit
+ bb.put((byte) 64);
+
+ // Src/Dst addresses
+ bb.put(srcAddr.getAddress());
+ bb.put(dstAddr.getAddress());
+
+ // Payload
+ payload.addPacketBytes(this, bb);
+
+ return getByteArrayFromBuffer(bb);
+ }
+
+ public int getProtocolId() {
+ return IPPROTO_IPV6;
+ }
+ }
+
+ public static class BytePayload implements Payload {
+ public final byte[] payload;
+
+ public BytePayload(byte[] payload) {
+ this.payload = payload;
+ }
+
+ public int getProtocolId() {
+ return -1;
+ }
+
+ public byte[] getPacketBytes(IpHeader header) {
+ ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN);
+
+ addPacketBytes(header, bb);
+ return getByteArrayFromBuffer(bb);
+ }
+
+ public void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) {
+ resultBuffer.put(payload);
+ }
+
+ public short length() {
+ return (short) payload.length;
+ }
+ }
+
+ public static class UdpHeader implements Payload {
+
+ public final short srcPort;
+ public final short dstPort;
+ public final Payload payload;
+
+ public UdpHeader(int srcPort, int dstPort, Payload payload) {
+ this.srcPort = (short) srcPort;
+ this.dstPort = (short) dstPort;
+ this.payload = payload;
+ }
+
+ public int getProtocolId() {
+ return IPPROTO_UDP;
+ }
+
+ public short length() {
+ return (short) (payload.length() + 8);
+ }
+
+ public byte[] getPacketBytes(IpHeader header) throws Exception {
+ ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN);
+
+ addPacketBytes(header, bb);
+ return getByteArrayFromBuffer(bb);
+ }
+
+ public void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) throws Exception {
+ // Source, Destination port
+ resultBuffer.putShort(srcPort);
+ resultBuffer.putShort(dstPort);
+
+ // Payload Length
+ resultBuffer.putShort(length());
+
+ // Get payload bytes for checksum + payload
+ ByteBuffer payloadBuffer = ByteBuffer.allocate(DATA_BUFFER_LEN);
+ payload.addPacketBytes(header, payloadBuffer);
+ byte[] payloadBytes = getByteArrayFromBuffer(payloadBuffer);
+
+ // Checksum
+ resultBuffer.putShort(calculateChecksum(header, payloadBytes));
+
+ // Payload
+ resultBuffer.put(payloadBytes);
+ }
+
+ private short calculateChecksum(IpHeader header, byte[] payloadBytes) throws Exception {
+ int newChecksum = 0;
+ ShortBuffer srcBuffer = ByteBuffer.wrap(header.srcAddr.getAddress()).asShortBuffer();
+ ShortBuffer dstBuffer = ByteBuffer.wrap(header.dstAddr.getAddress()).asShortBuffer();
+
+ while (srcBuffer.hasRemaining() || dstBuffer.hasRemaining()) {
+ short val = srcBuffer.hasRemaining() ? srcBuffer.get() : dstBuffer.get();
+
+ // Wrap as needed
+ newChecksum = addAndWrapForChecksum(newChecksum, val);
+ }
+
+ // Add pseudo-header values. Proto is 0-padded, so just use the byte.
+ newChecksum = addAndWrapForChecksum(newChecksum, header.proto);
+ newChecksum = addAndWrapForChecksum(newChecksum, length());
+ newChecksum = addAndWrapForChecksum(newChecksum, srcPort);
+ newChecksum = addAndWrapForChecksum(newChecksum, dstPort);
+ newChecksum = addAndWrapForChecksum(newChecksum, length());
+
+ ShortBuffer payloadShortBuffer = ByteBuffer.wrap(payloadBytes).asShortBuffer();
+ while (payloadShortBuffer.hasRemaining()) {
+ newChecksum = addAndWrapForChecksum(newChecksum, payloadShortBuffer.get());
+ }
+ if (payload.length() % 2 != 0) {
+ newChecksum =
+ addAndWrapForChecksum(
+ newChecksum, (payloadBytes[payloadBytes.length - 1] << 8));
+ }
+
+ return onesComplement(newChecksum);
+ }
+ }
+
+ public static class EspHeader implements Payload {
+ public final int nextHeader;
+ public final int spi;
+ public final int seqNum;
+ public final byte[] key;
+ public final byte[] payload;
+
+ /**
+ * Generic constructor for ESP headers.
+ *
+ * <p>For Tunnel mode, payload will be a full IP header + attached payloads
+ *
+ * <p>For Transport mode, payload will be only the attached payloads, but with the checksum
+ * calculated using the pre-encryption IP header
+ */
+ public EspHeader(int nextHeader, int spi, int seqNum, byte[] key, byte[] payload) {
+ this.nextHeader = nextHeader;
+ this.spi = spi;
+ this.seqNum = seqNum;
+ this.key = key;
+ this.payload = payload;
+ }
+
+ public int getProtocolId() {
+ return IPPROTO_ESP;
+ }
+
+ public short length() {
+ // ALWAYS uses AES-CBC, HMAC-SHA256 (128b trunc len)
+ return (short)
+ calculateEspPacketSize(payload.length, AES_CBC_IV_LEN, AES_CBC_BLK_SIZE, 128);
+ }
+
+ public byte[] getPacketBytes(IpHeader header) throws Exception {
+ ByteBuffer bb = ByteBuffer.allocate(DATA_BUFFER_LEN);
+
+ addPacketBytes(header, bb);
+ return getByteArrayFromBuffer(bb);
+ }
+
+ public void addPacketBytes(IpHeader header, ByteBuffer resultBuffer) throws Exception {
+ ByteBuffer espPayloadBuffer = ByteBuffer.allocate(DATA_BUFFER_LEN);
+ espPayloadBuffer.putInt(spi);
+ espPayloadBuffer.putInt(seqNum);
+ espPayloadBuffer.put(getCiphertext(key));
+
+ espPayloadBuffer.put(getIcv(getByteArrayFromBuffer(espPayloadBuffer)), 0, 16);
+ resultBuffer.put(getByteArrayFromBuffer(espPayloadBuffer));
+ }
+
+ private byte[] getIcv(byte[] authenticatedSection) throws GeneralSecurityException {
+ Mac sha256HMAC = Mac.getInstance(HMAC_SHA_256);
+ SecretKeySpec authKey = new SecretKeySpec(key, HMAC_SHA_256);
+ sha256HMAC.init(authKey);
+
+ return sha256HMAC.doFinal(authenticatedSection);
+ }
+
+ /**
+ * Encrypts and builds ciphertext block. Includes the IV, Padding and Next-Header blocks
+ *
+ * <p>The ciphertext does NOT include the SPI/Sequence numbers, or the ICV.
+ */
+ private byte[] getCiphertext(byte[] key) throws GeneralSecurityException {
+ int paddedLen = calculateEspEncryptedLength(payload.length, AES_CBC_BLK_SIZE);
+ ByteBuffer paddedPayload = ByteBuffer.allocate(paddedLen);
+ paddedPayload.put(payload);
+
+ // Add padding - consecutive integers from 0x01
+ int pad = 1;
+ while (paddedPayload.position() < paddedPayload.limit()) {
+ paddedPayload.put((byte) pad++);
+ }
+
+ paddedPayload.position(paddedPayload.limit() - 2);
+ paddedPayload.put((byte) (paddedLen - 2 - payload.length)); // Pad length
+ paddedPayload.put((byte) nextHeader);
+
+ // Generate Initialization Vector
+ byte[] iv = new byte[AES_CBC_IV_LEN];
+ new SecureRandom().nextBytes(iv);
+ IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
+ SecretKeySpec secretKeySpec = new SecretKeySpec(key, AES);
+
+ // Encrypt payload
+ Cipher cipher = Cipher.getInstance(AES_CBC);
+ cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
+ byte[] encrypted = cipher.doFinal(getByteArrayFromBuffer(paddedPayload));
+
+ // Build ciphertext
+ ByteBuffer cipherText = ByteBuffer.allocate(AES_CBC_IV_LEN + encrypted.length);
+ cipherText.put(iv);
+ cipherText.put(encrypted);
+
+ return getByteArrayFromBuffer(cipherText);
+ }
+ }
+
+ private static int addAndWrapForChecksum(int currentChecksum, int value) {
+ currentChecksum += value & 0x0000ffff;
+
+ // Wrap anything beyond the first 16 bits, and add to lower order bits
+ return (currentChecksum >>> 16) + (currentChecksum & 0x0000ffff);
+ }
+
+ private static short onesComplement(int val) {
+ val = (val >>> 16) + (val & 0xffff);
+
+ if (val == 0) return 0;
+ return (short) ((~val) & 0xffff);
+ }
+
+ public static int calculateEspPacketSize(
+ int payloadLen, int cryptIvLength, int cryptBlockSize, int authTruncLen) {
+ final int ESP_HDRLEN = 4 + 4; // SPI + Seq#
+ final int ICV_LEN = authTruncLen / 8; // Auth trailer; based on truncation length
+ payloadLen += cryptIvLength; // Initialization Vector
+
+ // Align to block size of encryption algorithm
+ payloadLen = calculateEspEncryptedLength(payloadLen, cryptBlockSize);
+ return payloadLen + ESP_HDRLEN + ICV_LEN;
+ }
+
+ private static int calculateEspEncryptedLength(int payloadLen, int cryptBlockSize) {
+ payloadLen += 2; // ESP trailer
+
+ // Align to block size of encryption algorithm
+ return payloadLen + calculateEspPadLen(payloadLen, cryptBlockSize);
+ }
+
+ private static int calculateEspPadLen(int payloadLen, int cryptBlockSize) {
+ return (cryptBlockSize - (payloadLen % cryptBlockSize)) % cryptBlockSize;
+ }
+
+ private static byte[] getByteArrayFromBuffer(ByteBuffer buffer) {
+ return Arrays.copyOfRange(buffer.array(), 0, buffer.position());
+ }
+
+ /*
+ * Debug printing
+ */
+ private static final char[] hexArray = "0123456789ABCDEF".toCharArray();
+
+ public static String bytesToHex(byte[] bytes) {
+ StringBuilder sb = new StringBuilder();
+ for (byte b : bytes) {
+ sb.append(hexArray[b >>> 4]);
+ sb.append(hexArray[b & 0x0F]);
+ sb.append(' ');
+ }
+ return sb.toString();
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java b/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
index 60ac226..01ac3fd 100644
--- a/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
+++ b/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
@@ -24,6 +24,7 @@
import javax.net.ssl.SSLPeerUnverifiedException;
import android.net.SSLCertificateSocketFactory;
+import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
import libcore.javax.net.ssl.SSLConfigurationAsserts;
@@ -91,7 +92,7 @@
// a host and port that are expected to be available but have
// a cert with a different CN, in this case CN=mail.google.com
- private static String TEST_CREATE_SOCKET_HOST = "googlemail.com";
+ private static String TEST_CREATE_SOCKET_HOST = "www3.l.google.com";
private static int TEST_CREATE_SOCKET_PORT = 443;
/**
@@ -101,6 +102,7 @@
*
* NOTE: Test will fail if external server is not available.
*/
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
public void test_createSocket_simple() throws Exception {
try {
mFactory.createSocket(TEST_CREATE_SOCKET_HOST, TEST_CREATE_SOCKET_PORT);
@@ -117,6 +119,7 @@
*
* NOTE: Test will fail if external server is not available.
*/
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
public void test_createSocket_wrapping() throws Exception {
try {
Socket underlying = new Socket(TEST_CREATE_SOCKET_HOST, TEST_CREATE_SOCKET_PORT);
@@ -135,6 +138,7 @@
*
* NOTE: Test will fail if external server is not available.
*/
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
public void test_createSocket_bind() throws Exception {
try {
mFactory.createSocket(TEST_CREATE_SOCKET_HOST, TEST_CREATE_SOCKET_PORT, null, 0);
diff --git a/tests/cts/net/src/android/net/cts/SysctlBaseTest.java b/tests/cts/net/src/android/net/cts/SysctlBaseTest.java
deleted file mode 100644
index a5966d4..0000000
--- a/tests/cts/net/src/android/net/cts/SysctlBaseTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.cts;
-
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-import android.system.StructStat;
-import android.test.AndroidTestCase;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-/**
- * Tests for multinetwork sysctl functionality.
- */
-public class SysctlBaseTest extends AndroidTestCase {
-
- // Expected mode, UID, and GID of sysctl files.
- private static final int SYSCTL_MODE = 0100644;
- private static final int SYSCTL_UID = 0;
- private static final int SYSCTL_GID = 0;
-
- private void checkSysctlPermissions(String fileName) throws ErrnoException {
- StructStat stat = Os.stat(fileName);
- assertEquals("mode of " + fileName + ":", SYSCTL_MODE, stat.st_mode);
- assertEquals("UID of " + fileName + ":", SYSCTL_UID, stat.st_uid);
- assertEquals("GID of " + fileName + ":", SYSCTL_GID, stat.st_gid);
- }
-
- protected void assertLess(String sysctl, int a, int b) {
- assertTrue("value of " + sysctl + ": expected < " + b + " but was: " + a, a < b);
- }
-
- protected void assertAtLeast(String sysctl, int a, int b) {
- assertTrue("value of " + sysctl + ": expected >= " + b + " but was: " + a, a >= b);
- }
-
- private String readFile(String fileName) throws ErrnoException, IOException {
- byte[] buf = new byte[1024];
- FileDescriptor fd = Os.open(fileName, 0, OsConstants.O_RDONLY);
- int bytesRead = Os.read(fd, buf, 0, buf.length);
- assertLess("length of " + fileName + ":", bytesRead, buf.length);
- return new String(buf);
- }
-
- /*
- * Checks permissions and retrieves the sysctl's value. Retrieval of value should always use
- * this method
- */
- protected int getIntValue(String filename) throws ErrnoException, IOException {
- checkSysctlPermissions(filename);
- return Integer.parseInt(readFile(filename).trim());
- }
-}
diff --git a/tests/cts/net/src/android/net/cts/TheaterModeTest.java b/tests/cts/net/src/android/net/cts/TheaterModeTest.java
index 10fca6f..d1ddeaa 100644
--- a/tests/cts/net/src/android/net/cts/TheaterModeTest.java
+++ b/tests/cts/net/src/android/net/cts/TheaterModeTest.java
@@ -18,12 +18,11 @@
import android.content.ContentResolver;
import android.content.Context;
+import android.platform.test.annotations.AppModeFull;
import android.provider.Settings;
import android.test.AndroidTestCase;
import android.util.Log;
-import java.lang.Thread;
-
public class TheaterModeTest extends AndroidTestCase {
private static final String TAG = "TheaterModeTest";
private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
@@ -40,6 +39,7 @@
|| mContext.getPackageManager().hasSystemFeature(FEATURE_WIFI));
}
+ @AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
public void testTheaterMode() {
setup();
if (!mHasFeature) {
diff --git a/tests/cts/net/src/android/net/cts/TrafficStatsTest.java b/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
index a8743fa..5bd1e20 100755
--- a/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
+++ b/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
@@ -16,14 +16,15 @@
package android.net.cts;
+import android.content.pm.PackageManager;
+import android.net.NetworkStats;
import android.net.TrafficStats;
import android.os.Process;
+import android.os.SystemProperties;
+import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
import android.util.Log;
-import java.io.BufferedReader;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -81,6 +82,7 @@
return packetCount * (20 + 32 + bytes);
}
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testTrafficStatsForLocalhost() throws IOException {
final long mobileTxPacketsBefore = TrafficStats.getMobileTxPackets();
final long mobileRxPacketsBefore = TrafficStats.getMobileRxPackets();
@@ -99,6 +101,7 @@
final int byteCount = 1024;
final int packetCount = 1024;
+ TrafficStats.startDataProfiling(null);
final ServerSocket server = new ServerSocket(0);
new Thread("TrafficStatsTest.testTrafficStatsForLocalhost") {
@Override
@@ -153,6 +156,7 @@
Thread.sleep(1000);
} catch (InterruptedException e) {
}
+ NetworkStats testStats = TrafficStats.stopDataProfiling(null);
long mobileTxPacketsAfter = TrafficStats.getMobileTxPackets();
long mobileRxPacketsAfter = TrafficStats.getMobileRxPackets();
@@ -194,6 +198,24 @@
Log.i(LOG_TAG, "lingering traffic data: " + deltaTxOtherPackets + "/" + deltaRxOtherPackets);
}
+ // Check the per uid stats read from data profiling have the stats expected. The data
+ // profiling snapshot is generated from readNetworkStatsDetail() method in
+ // networkStatsService and in this way we can verify the detail networkStats of a given uid
+ // is correct.
+ NetworkStats.Entry entry = testStats.getTotal(null, Process.myUid());
+ assertTrue("txPackets detail: " + entry.txPackets + " uidTxPackets: " + uidTxDeltaPackets,
+ entry.txPackets >= packetCount + minExpectedExtraPackets
+ && entry.txPackets <= uidTxDeltaPackets);
+ assertTrue("rxPackets detail: " + entry.rxPackets + " uidRxPackets: " + uidRxDeltaPackets,
+ entry.rxPackets >= packetCount + minExpectedExtraPackets
+ && entry.rxPackets <= uidRxDeltaPackets);
+ assertTrue("txBytes detail: " + entry.txBytes + " uidTxDeltaBytes: " + uidTxDeltaBytes,
+ entry.txBytes >= tcpPacketToIpBytes(packetCount, byteCount)
+ + tcpPacketToIpBytes(minExpectedExtraPackets, 0) && entry.txBytes <= uidTxDeltaBytes);
+ assertTrue("rxBytes detail: " + entry.rxBytes + " uidRxDeltaBytes: " + uidRxDeltaBytes,
+ entry.rxBytes >= tcpPacketToIpBytes(packetCount, byteCount)
+ + tcpPacketToIpBytes(minExpectedExtraPackets, 0) && entry.rxBytes <= uidRxDeltaBytes);
+
assertTrue("uidtxp: " + uidTxPacketsBefore + " -> " + uidTxPacketsAfter + " delta=" + uidTxDeltaPackets +
" Wanted: " + uidTxDeltaPackets + ">=" + packetCount + "+" + minExpectedExtraPackets + " && " +
uidTxDeltaPackets + "<=" + packetCount + "+" + packetCount + "+" + maxExpectedExtraPackets + "+" + deltaTxOtherPackets,
@@ -216,19 +238,37 @@
uidRxDeltaBytes <= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets + deltaRxOtherPackets, 0));
// Localhost traffic *does* count against total stats.
- // Fudge by 132 packets of 1500 bytes not related to the test.
+ // Check the total stats increased after test data transfer over localhost has been made.
assertTrue("ttxp: " + totalTxPacketsBefore + " -> " + totalTxPacketsAfter,
- totalTxPacketsAfter >= totalTxPacketsBefore + uidTxDeltaPackets &&
- totalTxPacketsAfter <= totalTxPacketsBefore + uidTxDeltaPackets + 132);
+ totalTxPacketsAfter >= totalTxPacketsBefore + uidTxDeltaPackets);
assertTrue("trxp: " + totalRxPacketsBefore + " -> " + totalRxPacketsAfter,
- totalRxPacketsAfter >= totalRxPacketsBefore + uidRxDeltaPackets &&
- totalRxPacketsAfter <= totalRxPacketsBefore + uidRxDeltaPackets + 132);
+ totalRxPacketsAfter >= totalRxPacketsBefore + uidRxDeltaPackets);
assertTrue("ttxb: " + totalTxBytesBefore + " -> " + totalTxBytesAfter,
- totalTxBytesAfter >= totalTxBytesBefore + uidTxDeltaBytes &&
- totalTxBytesAfter <= totalTxBytesBefore + uidTxDeltaBytes + 132 * 1500);
+ totalTxBytesAfter >= totalTxBytesBefore + uidTxDeltaBytes);
assertTrue("trxb: " + totalRxBytesBefore + " -> " + totalRxBytesAfter,
- totalRxBytesAfter >= totalRxBytesBefore + uidRxDeltaBytes &&
- totalRxBytesAfter <= totalRxBytesBefore + uidRxDeltaBytes + 132 * 1500);
+ totalRxBytesAfter >= totalRxBytesBefore + uidRxDeltaBytes);
+
+ // If the adb TCP port is opened, this test may be run by adb over network.
+ // Huge amount of data traffic might go through the network and accounted into total packets
+ // stats. The upper bound check would be meaningless.
+ // TODO: Consider precisely calculate the traffic accounted due to adb over network and
+ // subtract it when checking upper bound instead of skip checking.
+ final PackageManager pm = mContext.getPackageManager();
+ if (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1
+ || SystemProperties.getInt("service.adb.tcp.port", -1) > -1
+ || !pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY)) {
+ Log.i(LOG_TAG, "adb is running over the network, skip the upper bound check");
+ } else {
+ // Fudge by 132 packets of 1500 bytes not related to the test.
+ assertTrue("ttxp: " + totalTxPacketsBefore + " -> " + totalTxPacketsAfter,
+ totalTxPacketsAfter <= totalTxPacketsBefore + uidTxDeltaPackets + 132);
+ assertTrue("trxp: " + totalRxPacketsBefore + " -> " + totalRxPacketsAfter,
+ totalRxPacketsAfter <= totalRxPacketsBefore + uidRxDeltaPackets + 132);
+ assertTrue("ttxb: " + totalTxBytesBefore + " -> " + totalTxBytesAfter,
+ totalTxBytesAfter <= totalTxBytesBefore + uidTxDeltaBytes + 132 * 1500);
+ assertTrue("trxb: " + totalRxBytesBefore + " -> " + totalRxBytesAfter,
+ totalRxBytesAfter <= totalRxBytesBefore + uidRxDeltaBytes + 132 * 1500);
+ }
// Localhost traffic should *not* count against mobile stats,
// There might be some other traffic, but nowhere near 1MB.
@@ -244,6 +284,5 @@
assertTrue("mrxb: " + mobileRxBytesBefore + " -> " + mobileRxBytesAfter,
mobileRxBytesAfter >= mobileRxBytesBefore &&
mobileRxBytesAfter <= mobileRxBytesBefore + 200000);
-
}
}
diff --git a/tests/cts/net/src/android/net/cts/TunUtils.java b/tests/cts/net/src/android/net/cts/TunUtils.java
new file mode 100644
index 0000000..a030713
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/TunUtils.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import static android.net.cts.PacketUtils.IP4_HDRLEN;
+import static android.net.cts.PacketUtils.IP6_HDRLEN;
+import static android.net.cts.PacketUtils.IPPROTO_ESP;
+import static android.net.cts.PacketUtils.UDP_HDRLEN;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import android.os.ParcelFileDescriptor;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Predicate;
+
+public class TunUtils {
+ private static final String TAG = TunUtils.class.getSimpleName();
+
+ private static final int DATA_BUFFER_LEN = 4096;
+ private static final int TIMEOUT = 100;
+
+ private static final int IP4_PROTO_OFFSET = 9;
+ private static final int IP6_PROTO_OFFSET = 6;
+
+ private static final int IP4_ADDR_OFFSET = 12;
+ private static final int IP4_ADDR_LEN = 4;
+ private static final int IP6_ADDR_OFFSET = 8;
+ private static final int IP6_ADDR_LEN = 16;
+
+ private final ParcelFileDescriptor mTunFd;
+ private final List<byte[]> mPackets = new ArrayList<>();
+ private final Thread mReaderThread;
+
+ public TunUtils(ParcelFileDescriptor tunFd) {
+ mTunFd = tunFd;
+
+ // Start background reader thread
+ mReaderThread =
+ new Thread(
+ () -> {
+ try {
+ // Loop will exit and thread will quit when tunFd is closed.
+ // Receiving either EOF or an exception will exit this reader loop.
+ // FileInputStream in uninterruptable, so there's no good way to
+ // ensure that this thread shuts down except upon FD closure.
+ while (true) {
+ byte[] intercepted = receiveFromTun();
+ if (intercepted == null) {
+ // Exit once we've hit EOF
+ return;
+ } else if (intercepted.length > 0) {
+ // Only save packet if we've received any bytes.
+ synchronized (mPackets) {
+ mPackets.add(intercepted);
+ mPackets.notifyAll();
+ }
+ }
+ }
+ } catch (IOException ignored) {
+ // Simply exit this reader thread
+ return;
+ }
+ });
+ mReaderThread.start();
+ }
+
+ private byte[] receiveFromTun() throws IOException {
+ FileInputStream in = new FileInputStream(mTunFd.getFileDescriptor());
+ byte[] inBytes = new byte[DATA_BUFFER_LEN];
+ int bytesRead = in.read(inBytes);
+
+ if (bytesRead < 0) {
+ return null; // return null for EOF
+ } else if (bytesRead >= DATA_BUFFER_LEN) {
+ throw new IllegalStateException("Too big packet. Fragmentation unsupported");
+ }
+ return Arrays.copyOf(inBytes, bytesRead);
+ }
+
+ private byte[] getFirstMatchingPacket(Predicate<byte[]> verifier, int startIndex) {
+ synchronized (mPackets) {
+ for (int i = startIndex; i < mPackets.size(); i++) {
+ byte[] pkt = mPackets.get(i);
+ if (verifier.test(pkt)) {
+ return pkt;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Checks if the specified bytes were ever sent in plaintext.
+ *
+ * <p>Only checks for known plaintext bytes to prevent triggering on ICMP/RA packets or the like
+ *
+ * @param plaintext the plaintext bytes to check for
+ * @param startIndex the index in the list to check for
+ */
+ public boolean hasPlaintextPacket(byte[] plaintext, int startIndex) {
+ Predicate<byte[]> verifier =
+ (pkt) -> {
+ return Collections.indexOfSubList(Arrays.asList(pkt), Arrays.asList(plaintext))
+ != -1;
+ };
+ return getFirstMatchingPacket(verifier, startIndex) != null;
+ }
+
+ public byte[] getEspPacket(int spi, boolean encap, int startIndex) {
+ return getFirstMatchingPacket(
+ (pkt) -> {
+ return isEsp(pkt, spi, encap);
+ },
+ startIndex);
+ }
+
+ public byte[] awaitEspPacketNoPlaintext(
+ int spi, byte[] plaintext, boolean useEncap, int expectedPacketSize) throws Exception {
+ long endTime = System.currentTimeMillis() + TIMEOUT;
+ int startIndex = 0;
+
+ synchronized (mPackets) {
+ while (System.currentTimeMillis() < endTime) {
+ byte[] espPkt = getEspPacket(spi, useEncap, startIndex);
+ if (espPkt != null) {
+ // Validate packet size
+ assertEquals(expectedPacketSize, espPkt.length);
+
+ // Always check plaintext from start
+ assertFalse(hasPlaintextPacket(plaintext, 0));
+ return espPkt; // We've found the packet we're looking for.
+ }
+
+ startIndex = mPackets.size();
+
+ // Try to prevent waiting too long. If waitTimeout <= 0, we've already hit timeout
+ long waitTimeout = endTime - System.currentTimeMillis();
+ if (waitTimeout > 0) {
+ mPackets.wait(waitTimeout);
+ }
+ }
+
+ fail("No such ESP packet found with SPI " + spi);
+ }
+ return null;
+ }
+
+ private static boolean isSpiEqual(byte[] pkt, int espOffset, int spi) {
+ // Check SPI byte by byte.
+ return pkt[espOffset] == (byte) ((spi >>> 24) & 0xff)
+ && pkt[espOffset + 1] == (byte) ((spi >>> 16) & 0xff)
+ && pkt[espOffset + 2] == (byte) ((spi >>> 8) & 0xff)
+ && pkt[espOffset + 3] == (byte) (spi & 0xff);
+ }
+
+ private static boolean isEsp(byte[] pkt, int spi, boolean encap) {
+ if (isIpv6(pkt)) {
+ // IPv6 UDP encap not supported by kernels; assume non-encap.
+ return pkt[IP6_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP6_HDRLEN, spi);
+ } else {
+ // Use default IPv4 header length (assuming no options)
+ if (encap) {
+ return pkt[IP4_PROTO_OFFSET] == IPPROTO_UDP
+ && isSpiEqual(pkt, IP4_HDRLEN + UDP_HDRLEN, spi);
+ } else {
+ return pkt[IP4_PROTO_OFFSET] == IPPROTO_ESP && isSpiEqual(pkt, IP4_HDRLEN, spi);
+ }
+ }
+ }
+
+ private static boolean isIpv6(byte[] pkt) {
+ // First nibble shows IP version. 0x60 for IPv6
+ return (pkt[0] & (byte) 0xF0) == (byte) 0x60;
+ }
+
+ private static byte[] getReflectedPacket(byte[] pkt) {
+ byte[] reflected = Arrays.copyOf(pkt, pkt.length);
+
+ if (isIpv6(pkt)) {
+ // Set reflected packet's dst to that of the original's src
+ System.arraycopy(
+ pkt, // src
+ IP6_ADDR_OFFSET + IP6_ADDR_LEN, // src offset
+ reflected, // dst
+ IP6_ADDR_OFFSET, // dst offset
+ IP6_ADDR_LEN); // len
+ // Set reflected packet's src IP to that of the original's dst IP
+ System.arraycopy(
+ pkt, // src
+ IP6_ADDR_OFFSET, // src offset
+ reflected, // dst
+ IP6_ADDR_OFFSET + IP6_ADDR_LEN, // dst offset
+ IP6_ADDR_LEN); // len
+ } else {
+ // Set reflected packet's dst to that of the original's src
+ System.arraycopy(
+ pkt, // src
+ IP4_ADDR_OFFSET + IP4_ADDR_LEN, // src offset
+ reflected, // dst
+ IP4_ADDR_OFFSET, // dst offset
+ IP4_ADDR_LEN); // len
+ // Set reflected packet's src IP to that of the original's dst IP
+ System.arraycopy(
+ pkt, // src
+ IP4_ADDR_OFFSET, // src offset
+ reflected, // dst
+ IP4_ADDR_OFFSET + IP4_ADDR_LEN, // dst offset
+ IP4_ADDR_LEN); // len
+ }
+ return reflected;
+ }
+
+ /** Takes all captured packets, flips the src/dst, and re-injects them. */
+ public void reflectPackets() throws IOException {
+ synchronized (mPackets) {
+ for (byte[] pkt : mPackets) {
+ injectPacket(getReflectedPacket(pkt));
+ }
+ }
+ }
+
+ public void injectPacket(byte[] pkt) throws IOException {
+ FileOutputStream out = new FileOutputStream(mTunFd.getFileDescriptor());
+ out.write(pkt);
+ out.flush();
+ }
+
+ /** Resets the intercepted packets. */
+ public void reset() throws IOException {
+ synchronized (mPackets) {
+ mPackets.clear();
+ }
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/UriTest.java b/tests/cts/net/src/android/net/cts/UriTest.java
index 5c54cda..40b8fb7 100644
--- a/tests/cts/net/src/android/net/cts/UriTest.java
+++ b/tests/cts/net/src/android/net/cts/UriTest.java
@@ -22,6 +22,7 @@
import android.test.AndroidTestCase;
import java.io.File;
import java.util.Arrays;
+import java.util.ArrayList;
public class UriTest extends AndroidTestCase {
public void testParcelling() {
@@ -73,6 +74,12 @@
assertEquals("new", b.getFragment());
assertEquals("bar", b.getSchemeSpecificPart());
assertEquals("foo", b.getScheme());
+
+ a = Uri.fromParts("scheme", "[2001:db8::dead:e1f]/foo", "bar");
+ b = a.buildUpon().fragment("qux").build();
+ assertEquals("qux", b.getFragment());
+ assertEquals("[2001:db8::dead:e1f]/foo", b.getSchemeSpecificPart());
+ assertEquals("scheme", b.getScheme());
}
public void testStringUri() {
@@ -120,6 +127,38 @@
assertEquals("a.foo.com", uri.getHost());
assertEquals(-1, uri.getPort());
assertEquals("\\.example.com/path", uri.getPath());
+
+ uri = Uri.parse("https://[2001:db8::dead:e1f]/foo");
+ assertEquals("[2001:db8::dead:e1f]", uri.getAuthority());
+ assertNull(uri.getUserInfo());
+ assertEquals("[2001:db8::dead:e1f]", uri.getHost());
+ assertEquals(-1, uri.getPort());
+ assertEquals("/foo", uri.getPath());
+ assertEquals(null, uri.getFragment());
+ assertEquals("//[2001:db8::dead:e1f]/foo", uri.getSchemeSpecificPart());
+
+ uri = Uri.parse("https://[2001:db8::dead:e1f]/#foo");
+ assertEquals("[2001:db8::dead:e1f]", uri.getAuthority());
+ assertNull(uri.getUserInfo());
+ assertEquals("[2001:db8::dead:e1f]", uri.getHost());
+ assertEquals(-1, uri.getPort());
+ assertEquals("/", uri.getPath());
+ assertEquals("foo", uri.getFragment());
+ assertEquals("//[2001:db8::dead:e1f]/", uri.getSchemeSpecificPart());
+
+ uri = Uri.parse(
+ "https://some:user@[2001:db8::dead:e1f]:1234/foo?corge=thud&corge=garp#bar");
+ assertEquals("some:user@[2001:db8::dead:e1f]:1234", uri.getAuthority());
+ assertEquals("some:user", uri.getUserInfo());
+ assertEquals("[2001:db8::dead:e1f]", uri.getHost());
+ assertEquals(1234, uri.getPort());
+ assertEquals("/foo", uri.getPath());
+ assertEquals("bar", uri.getFragment());
+ assertEquals("//some:user@[2001:db8::dead:e1f]:1234/foo?corge=thud&corge=garp",
+ uri.getSchemeSpecificPart());
+ assertEquals("corge=thud&corge=garp", uri.getQuery());
+ assertEquals("thud", uri.getQueryParameter("corge"));
+ assertEquals(Arrays.asList("thud", "garp"), uri.getQueryParameters("corge"));
}
public void testCompareTo() {
@@ -164,22 +203,62 @@
String encoded = Uri.encode("Bob:/", "/");
assertEquals(-1, encoded.indexOf(':'));
assertTrue(encoded.indexOf('/') > -1);
- assertDecode(null);
- assertDecode("");
- assertDecode("Bob");
- assertDecode(":Bob");
- assertDecode("::Bob");
- assertDecode("Bob::Lee");
- assertDecode("Bob:Lee");
- assertDecode("Bob::");
- assertDecode("Bob:");
- assertDecode("::Bob::");
+ assertEncodeDecodeRoundtripExact(null);
+ assertEncodeDecodeRoundtripExact("");
+ assertEncodeDecodeRoundtripExact("Bob");
+ assertEncodeDecodeRoundtripExact(":Bob");
+ assertEncodeDecodeRoundtripExact("::Bob");
+ assertEncodeDecodeRoundtripExact("Bob::Lee");
+ assertEncodeDecodeRoundtripExact("Bob:Lee");
+ assertEncodeDecodeRoundtripExact("Bob::");
+ assertEncodeDecodeRoundtripExact("Bob:");
+ assertEncodeDecodeRoundtripExact("::Bob::");
+ assertEncodeDecodeRoundtripExact("https:/some:user@[2001:db8::dead:e1f]:1234/foo#bar");
}
- private void assertDecode(String s) {
+ private static void assertEncodeDecodeRoundtripExact(String s) {
assertEquals(s, Uri.decode(Uri.encode(s, null)));
}
+ public void testDecode_emptyString_returnsEmptyString() {
+ assertEquals("", Uri.decode(""));
+ }
+
+ public void testDecode_null_returnsNull() {
+ assertNull(Uri.decode(null));
+ }
+
+ public void testDecode_wrongHexDigit() {
+ // %p in the end.
+ assertEquals("ab/$\u0102%\u0840\uFFFD\u0000", Uri.decode("ab%2f$%C4%82%25%e0%a1%80%p"));
+ }
+
+ public void testDecode_secondHexDigitWrong() {
+ // %1p in the end.
+ assertEquals("ab/$\u0102%\u0840\uFFFD\u0001", Uri.decode("ab%2f$%c4%82%25%e0%a1%80%1p"));
+ }
+
+ public void testDecode_endsWithPercent_appendsUnknownCharacter() {
+ // % in the end.
+ assertEquals("ab/$\u0102%\u0840\uFFFD", Uri.decode("ab%2f$%c4%82%25%e0%a1%80%"));
+ }
+
+ public void testDecode_plusNotConverted() {
+ assertEquals("ab/$\u0102%+\u0840", Uri.decode("ab%2f$%c4%82%25+%e0%a1%80"));
+ }
+
+ // Last character needs decoding (make sure we are flushing the buffer with chars to decode).
+ public void testDecode_lastCharacter() {
+ assertEquals("ab/$\u0102%\u0840", Uri.decode("ab%2f$%c4%82%25%e0%a1%80"));
+ }
+
+ // Check that a second row of encoded characters is decoded properly (internal buffers are
+ // reset properly).
+ public void testDecode_secondRowOfEncoded() {
+ assertEquals("ab/$\u0102%\u0840aa\u0840",
+ Uri.decode("ab%2f$%c4%82%25%e0%a1%80aa%e0%a1%80"));
+ }
+
public void testFromFile() {
File f = new File("/tmp/bob");
Uri uri = Uri.fromFile(f);
@@ -425,4 +504,87 @@
Uri.parse("HTTP://USER@WWW.ANDROID.COM:100/ABOUT?foo=blah@bar=bleh#c")
.normalizeScheme());
}
+
+ public void testToSafeString_tel() {
+ checkToSafeString("tel:xxxxxx", "tel:Google");
+ checkToSafeString("tel:xxxxxxxxxx", "tel:1234567890");
+ checkToSafeString("tEl:xxx.xxx-xxxx", "tEl:123.456-7890");
+ }
+
+ public void testToSafeString_sip() {
+ checkToSafeString("sip:xxxxxxx@xxxxxxx.xxxxxxxx", "sip:android@android.com:1234");
+ checkToSafeString("sIp:xxxxxxx@xxxxxxx.xxx", "sIp:android@android.com");
+ }
+
+ public void testToSafeString_sms() {
+ checkToSafeString("sms:xxxxxx", "sms:123abc");
+ checkToSafeString("smS:xxx.xxx-xxxx", "smS:123.456-7890");
+ }
+
+ public void testToSafeString_smsto() {
+ checkToSafeString("smsto:xxxxxx", "smsto:123abc");
+ checkToSafeString("SMSTo:xxx.xxx-xxxx", "SMSTo:123.456-7890");
+ }
+
+ public void testToSafeString_mailto() {
+ checkToSafeString("mailto:xxxxxxx@xxxxxxx.xxx", "mailto:android@android.com");
+ checkToSafeString("Mailto:xxxxxxx@xxxxxxx.xxxxxxxxxx",
+ "Mailto:android@android.com/secret");
+ }
+
+ public void testToSafeString_nfc() {
+ checkToSafeString("nfc:xxxxxx", "nfc:123abc");
+ checkToSafeString("nfc:xxx.xxx-xxxx", "nfc:123.456-7890");
+ checkToSafeString("nfc:xxxxxxx@xxxxxxx.xxx", "nfc:android@android.com");
+ }
+
+ public void testToSafeString_http() {
+ checkToSafeString("http://www.android.com/...", "http://www.android.com");
+ checkToSafeString("HTTP://www.android.com/...", "HTTP://www.android.com");
+ checkToSafeString("http://www.android.com/...", "http://www.android.com/");
+ checkToSafeString("http://www.android.com/...", "http://www.android.com/secretUrl?param");
+ checkToSafeString("http://www.android.com/...",
+ "http://user:pwd@www.android.com/secretUrl?param");
+ checkToSafeString("http://www.android.com/...",
+ "http://user@www.android.com/secretUrl?param");
+ checkToSafeString("http://www.android.com/...", "http://www.android.com/secretUrl?param");
+ checkToSafeString("http:///...", "http:///path?param");
+ checkToSafeString("http:///...", "http://");
+ checkToSafeString("http://:12345/...", "http://:12345/");
+ }
+
+ public void testToSafeString_https() {
+ checkToSafeString("https://www.android.com/...", "https://www.android.com/secretUrl?param");
+ checkToSafeString("https://www.android.com:8443/...",
+ "https://user:pwd@www.android.com:8443/secretUrl?param");
+ checkToSafeString("https://www.android.com/...", "https://user:pwd@www.android.com");
+ checkToSafeString("Https://www.android.com/...", "Https://user:pwd@www.android.com");
+ }
+
+ public void testToSafeString_ftp() {
+ checkToSafeString("ftp://ftp.android.com/...", "ftp://ftp.android.com/");
+ checkToSafeString("ftP://ftp.android.com/...", "ftP://anonymous@ftp.android.com/");
+ checkToSafeString("ftp://ftp.android.com:2121/...",
+ "ftp://root:love@ftp.android.com:2121/");
+ }
+
+ public void testToSafeString_rtsp() {
+ checkToSafeString("rtsp://rtsp.android.com/...", "rtsp://rtsp.android.com/");
+ checkToSafeString("rtsp://rtsp.android.com/...", "rtsp://rtsp.android.com/video.mov");
+ checkToSafeString("rtsp://rtsp.android.com/...", "rtsp://rtsp.android.com/video.mov?param");
+ checkToSafeString("RtsP://rtsp.android.com/...", "RtsP://anonymous@rtsp.android.com/");
+ checkToSafeString("rtsp://rtsp.android.com:2121/...",
+ "rtsp://username:password@rtsp.android.com:2121/");
+ }
+
+ public void testToSafeString_notSupport() {
+ checkToSafeString("unsupported://ajkakjah/askdha/secret?secret",
+ "unsupported://ajkakjah/askdha/secret?secret");
+ checkToSafeString("unsupported:ajkakjah/askdha/secret?secret",
+ "unsupported:ajkakjah/askdha/secret?secret");
+ }
+
+ private void checkToSafeString(String expectedSafeString, String original) {
+ assertEquals(expectedSafeString, Uri.parse(original).toSafeString());
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java b/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java
index 7076ea2..2d615bb 100644
--- a/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java
+++ b/tests/cts/net/src/android/net/cts/UrlQuerySanitizerTest.java
@@ -170,6 +170,12 @@
String initialPercentSign = "title=%B5";
assertEquals(expectedPlus, uqs.unescape(initialPlus));
assertEquals(expectedPercentSignHex, uqs.unescape(initialPercentSign));
+ String expectedPlusThenPercentSign = "Joe Random, User";
+ String plusThenPercentSign = "Joe+Random%2C%20User";
+ assertEquals(expectedPlusThenPercentSign, uqs.unescape(plusThenPercentSign));
+ String expectedPercentSignThenPlus = "Joe, Random User";
+ String percentSignThenPlus = "Joe%2C+Random+User";
+ assertEquals(expectedPercentSignThenPlus, uqs.unescape(percentSignThenPlus));
assertTrue(uqs.decodeHexDigit('0') >= 0);
assertTrue(uqs.decodeHexDigit('b') >= 0);
diff --git a/tests/cts/net/src/android/net/cts/VpnServiceTest.java b/tests/cts/net/src/android/net/cts/VpnServiceTest.java
index 8bdd7b0..15af23c 100644
--- a/tests/cts/net/src/android/net/cts/VpnServiceTest.java
+++ b/tests/cts/net/src/android/net/cts/VpnServiceTest.java
@@ -18,6 +18,7 @@
import android.content.Intent;
import android.net.VpnService;
import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
import java.io.File;
@@ -35,6 +36,7 @@
private VpnService mVpnService = new VpnService();
+ @AppModeFull(reason = "PackageManager#queryIntentActivities cannot access in instant app mode")
public void testPrepare() throws Exception {
// Should never return null since we are not prepared.
Intent intent = VpnService.prepare(mContext);
@@ -60,6 +62,7 @@
}
}
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testProtect_DatagramSocket() throws Exception {
DatagramSocket socket = new DatagramSocket();
try {
@@ -88,6 +91,7 @@
}
}
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testProtect_int() throws Exception {
DatagramSocket socket = new DatagramSocket();
ParcelFileDescriptor descriptor = ParcelFileDescriptor.fromDatagramSocket(socket);
diff --git a/tests/cts/net/src/android/net/http/cts/ApacheHttpClientTest.java b/tests/cts/net/src/android/net/http/cts/ApacheHttpClientTest.java
index 2e5306d..8d7dff0 100644
--- a/tests/cts/net/src/android/net/http/cts/ApacheHttpClientTest.java
+++ b/tests/cts/net/src/android/net/http/cts/ApacheHttpClientTest.java
@@ -22,6 +22,7 @@
import org.apache.http.impl.client.DefaultHttpClient;
import android.net.Uri;
+import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
import android.webkit.cts.CtsTestServer;
@@ -30,6 +31,7 @@
import java.util.ArrayList;
import java.util.List;
+@AppModeFull(reason = "Socket cannot bind in instant app mode")
public class ApacheHttpClientTest extends AndroidTestCase {
private static final int NUM_DOWNLOADS = 20;
diff --git a/tests/cts/net/src/android/net/http/cts/HttpResponseCacheTest.java b/tests/cts/net/src/android/net/http/cts/HttpResponseCacheTest.java
index 198f973..354954e 100644
--- a/tests/cts/net/src/android/net/http/cts/HttpResponseCacheTest.java
+++ b/tests/cts/net/src/android/net/http/cts/HttpResponseCacheTest.java
@@ -22,6 +22,7 @@
import junit.framework.TestCase;
import android.net.http.HttpResponseCache;
+import android.platform.test.annotations.AppModeFull;
import com.android.compatibility.common.util.FileUtils;
@@ -126,6 +127,7 @@
* Make sure that statistics tracking are wired all the way through the
* wrapper class. http://code.google.com/p/android/issues/detail?id=25418
*/
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
public void testStatisticsTracking() throws Exception {
HttpResponseCache cache = HttpResponseCache.install(cacheDir, 10 * 1024 * 1024);
diff --git a/tests/cts/net/src/android/net/http/cts/SslCertificateTest.java b/tests/cts/net/src/android/net/http/cts/SslCertificateTest.java
index 70ae496..95f415c 100644
--- a/tests/cts/net/src/android/net/http/cts/SslCertificateTest.java
+++ b/tests/cts/net/src/android/net/http/cts/SslCertificateTest.java
@@ -230,6 +230,19 @@
final String EXPECTED = "Issued to: c=ccc,o=testOName,ou=testUName,cn=testCName;\n"
+ "Issued by: e=aeei,c=adb,o=testOName,ou=testUName,cn=testCName;\n";
assertEquals(EXPECTED, ssl.toString());
+ assertNull(ssl.getX509Certificate());
}
+ public void testGetX509Certificate() {
+ final String TO = "c=ccc,o=testOName,ou=testUName,cn=testCName";
+ final String BY = "e=aeei,c=adb,o=testOName,ou=testUName,cn=testCName";
+ Date validNotBefore = new Date(System.currentTimeMillis() - 1000);
+ Date validNotAfter = new Date(System.currentTimeMillis());
+ SslCertificate ssl = new SslCertificate(TO, BY, validNotBefore, validNotAfter);
+ assertNull(ssl.getX509Certificate());
+
+ X509Certificate cert = new MockX509Certificate();
+ ssl = new SslCertificate(cert);
+ assertSame(cert, ssl.getX509Certificate());
+ }
}
diff --git a/tests/cts/net/src/android/net/http/cts/SslErrorTest.java b/tests/cts/net/src/android/net/http/cts/SslErrorTest.java
index 850a8b6..0058c90 100644
--- a/tests/cts/net/src/android/net/http/cts/SslErrorTest.java
+++ b/tests/cts/net/src/android/net/http/cts/SslErrorTest.java
@@ -77,4 +77,9 @@
SslError error = new SslError(SslError.SSL_EXPIRED, mCertificate);
assertEquals(error.getUrl(), "");
}
+
+ public void testGetCertificate() {
+ SslError error = new SslError(SslError.SSL_EXPIRED, mCertificate);
+ assertEquals(mCertificate, error.getCertificate());
+ }
}
diff --git a/tests/cts/net/src/android/net/ipv6/cts/PingTest.java b/tests/cts/net/src/android/net/ipv6/cts/PingTest.java
index c23ad30..146fd83 100644
--- a/tests/cts/net/src/android/net/ipv6/cts/PingTest.java
+++ b/tests/cts/net/src/android/net/ipv6/cts/PingTest.java
@@ -61,7 +61,7 @@
/** The beginning of an ICMPv6 echo request: type, code, and uninitialized checksum. */
private static final byte[] PING_HEADER = new byte[] {
- (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00
+ (byte) ICMP6_ECHO_REQUEST, (byte) 0x00, (byte) 0x00, (byte) 0x00
};
/**
@@ -135,7 +135,7 @@
byte[] response = new byte[bytesRead];
responseBuffer.flip();
responseBuffer.get(response, 0, bytesRead);
- assertEquals((byte) 0x81, response[0]);
+ assertEquals((byte) ICMP6_ECHO_REPLY, response[0]);
// Find out what ICMP ID was used in the packet that was sent.
int id = ((InetSocketAddress) Os.getsockname(s)).getPort();
diff --git a/tests/cts/net/src/android/net/rtp/cts/AudioGroupTest.java b/tests/cts/net/src/android/net/rtp/cts/AudioGroupTest.java
index f06d7e9..1bd7fad 100644
--- a/tests/cts/net/src/android/net/rtp/cts/AudioGroupTest.java
+++ b/tests/cts/net/src/android/net/rtp/cts/AudioGroupTest.java
@@ -21,13 +21,14 @@
import android.net.rtp.AudioGroup;
import android.net.rtp.AudioStream;
import android.net.rtp.RtpStream;
+import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
-import android.util.Log;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
+@AppModeFull(reason = "RtpStream cannot create in instant app mode")
public class AudioGroupTest extends AndroidTestCase {
private static final String TAG = AudioGroupTest.class.getSimpleName();
diff --git a/tests/cts/net/src/android/net/rtp/cts/AudioStreamTest.java b/tests/cts/net/src/android/net/rtp/cts/AudioStreamTest.java
index 323b022..f2db6ee 100644
--- a/tests/cts/net/src/android/net/rtp/cts/AudioStreamTest.java
+++ b/tests/cts/net/src/android/net/rtp/cts/AudioStreamTest.java
@@ -17,10 +17,12 @@
import android.net.rtp.AudioCodec;
import android.net.rtp.AudioStream;
+import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
import java.net.InetAddress;
+@AppModeFull(reason = "RtpStream cannot create in instant app mode")
public class AudioStreamTest extends AndroidTestCase {
private void testRtpStream(InetAddress address) throws Exception {
diff --git a/tests/cts/net/src/android/net/wifi/OWNERS b/tests/cts/net/src/android/net/wifi/OWNERS
new file mode 100644
index 0000000..4a6001b
--- /dev/null
+++ b/tests/cts/net/src/android/net/wifi/OWNERS
@@ -0,0 +1,5 @@
+etancohen@google.com
+lorenzo@google.com
+mplass@google.com
+rpius@google.com
+satk@google.com
diff --git a/tests/cts/net/src/android/net/wifi/aware/OWNERS b/tests/cts/net/src/android/net/wifi/aware/OWNERS
deleted file mode 100644
index 4afc47f..0000000
--- a/tests/cts/net/src/android/net/wifi/aware/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-etancohen@google.com
-satk@google.com
\ No newline at end of file
diff --git a/tests/cts/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/cts/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
index 7277553..1901f02 100644
--- a/tests/cts/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
+++ b/tests/cts/net/src/android/net/wifi/aware/cts/SingleDeviceTest.java
@@ -39,8 +39,12 @@
import android.net.wifi.aware.WifiAwareSession;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
+import com.android.compatibility.common.util.SystemUtil;
+
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
@@ -53,11 +57,12 @@
* Wi-Fi Aware CTS test suite: single device testing. Performs tests on a single
* device to validate Wi-Fi Aware.
*/
+@AppModeFull(reason = "Cannot get WifiAwareManager in instant app mode")
public class SingleDeviceTest extends AndroidTestCase {
private static final String TAG = "WifiAwareCtsTests";
- // wait for Wi-Fi Aware to become available
- static private final int WAIT_FOR_AWARE_CHANGE_SECS = 10;
+ // wait for Wi-Fi Aware state changes & network requests callbacks
+ static private final int WAIT_FOR_AWARE_CHANGE_SECS = 10; // 10 seconds
private final Object mLock = new Object();
private final HandlerThread mHandlerThread = new HandlerThread("SingleDeviceTest");
@@ -363,7 +368,7 @@
mWifiLock = mWifiManager.createWifiLock(TAG);
mWifiLock.acquire();
if (!mWifiManager.isWifiEnabled()) {
- mWifiManager.setWifiEnabled(true);
+ SystemUtil.runShellCommand("svc wifi enable");
}
mConnectivityManager = (ConnectivityManager) getContext().getSystemService(
@@ -433,7 +438,7 @@
// 1. Disable Wi-Fi
WifiAwareBroadcastReceiver receiver1 = new WifiAwareBroadcastReceiver();
mContext.registerReceiver(receiver1, intentFilter);
- mWifiManager.setWifiEnabled(false);
+ SystemUtil.runShellCommand("svc wifi disable");
assertTrue("Timeout waiting for Wi-Fi Aware to change status",
receiver1.waitForStateChange());
@@ -442,7 +447,7 @@
// 2. Enable Wi-Fi
WifiAwareBroadcastReceiver receiver2 = new WifiAwareBroadcastReceiver();
mContext.registerReceiver(receiver2, intentFilter);
- mWifiManager.setWifiEnabled(true);
+ SystemUtil.runShellCommand("svc wifi enable");
assertTrue("Timeout waiting for Wi-Fi Aware to change status",
receiver2.waitForStateChange());
@@ -702,7 +707,7 @@
/**
* Request an Aware data-path (open) as a Responder with an arbitrary peer MAC address. Validate
- * that times-out.
+ * that receive an onUnavailable() callback.
*/
public void testDataPathOpenOutOfBandFail() {
if (!TestUtils.shouldTestWifiAware(getContext())) {
@@ -710,28 +715,32 @@
}
MacAddress mac = MacAddress.fromString("00:01:02:03:04:05");
+ // 1. initialize Aware: only purpose is to make sure it is available for OOB data-path
WifiAwareSession session = attachAndGetSession();
PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(
"ValidName").build();
DiscoverySessionCallbackTest discoveryCb = new DiscoverySessionCallbackTest();
- NetworkCallbackTest networkCb = new NetworkCallbackTest();
+ session.publish(publishConfig, discoveryCb, mHandler);
+ assertTrue("Publish started",
+ discoveryCb.waitForCallback(DiscoverySessionCallbackTest.ON_PUBLISH_STARTED));
- // 1. request an AWARE network
+ // 2. request an AWARE network
+ NetworkCallbackTest networkCb = new NetworkCallbackTest();
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
session.createNetworkSpecifierOpen(
- WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
+ WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR,
mac.toByteArray())).build();
- mConnectivityManager.requestNetwork(nr, networkCb, 2000);
- assertTrue("OnUnavailable received", networkCb.waitForOnUnavailable());
+ mConnectivityManager.requestNetwork(nr, networkCb);
+ assertTrue("OnUnavailable not received", networkCb.waitForOnUnavailable());
session.close();
}
/**
* Request an Aware data-path (encrypted) as a Responder with an arbitrary peer MAC address.
- * Validate that times-out.
+ * Validate that receive an onUnavailable() callback.
*/
public void testDataPathPassphraseOutOfBandFail() {
if (!TestUtils.shouldTestWifiAware(getContext())) {
@@ -739,21 +748,25 @@
}
MacAddress mac = MacAddress.fromString("00:01:02:03:04:05");
+ // 1. initialize Aware: only purpose is to make sure it is available for OOB data-path
WifiAwareSession session = attachAndGetSession();
PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(
"ValidName").build();
DiscoverySessionCallbackTest discoveryCb = new DiscoverySessionCallbackTest();
- NetworkCallbackTest networkCb = new NetworkCallbackTest();
+ session.publish(publishConfig, discoveryCb, mHandler);
+ assertTrue("Publish started",
+ discoveryCb.waitForCallback(DiscoverySessionCallbackTest.ON_PUBLISH_STARTED));
- // 1. request an AWARE network
+ // 2. request an AWARE network
+ NetworkCallbackTest networkCb = new NetworkCallbackTest();
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
session.createNetworkSpecifierPassphrase(
- WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, mac.toByteArray(),
+ WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, mac.toByteArray(),
"abcdefghihk")).build();
- mConnectivityManager.requestNetwork(nr, networkCb, 2000);
- assertTrue("OnUnavailable received", networkCb.waitForOnUnavailable());
+ mConnectivityManager.requestNetwork(nr, networkCb);
+ assertTrue("OnUnavailable not received", networkCb.waitForOnUnavailable());
session.close();
}
diff --git a/tests/cts/net/src/android/net/wifi/cts/ConcurrencyTest.java b/tests/cts/net/src/android/net/wifi/cts/ConcurrencyTest.java
index a066ba8..628571c 100644
--- a/tests/cts/net/src/android/net/wifi/cts/ConcurrencyTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/ConcurrencyTest.java
@@ -16,34 +16,68 @@
package android.net.wifi.cts;
+import static org.junit.Assert.assertNotEquals;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.wifi.WifiManager;
import android.net.wifi.p2p.WifiP2pManager;
-import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_DISABLED;
-import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_STATE_ENABLED;
+import android.provider.Settings;
+import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
+import android.util.Log;
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.LinkedList;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public class ConcurrencyTest extends AndroidTestCase {
private class MySync {
- int expectedWifiState;
- int expectedP2pState;
+ static final int WIFI_STATE = 0;
+ static final int P2P_STATE = 1;
+ static final int DISCOVERY_STATE = 2;
+ static final int NETWORK_INFO = 3;
+
+ public BitSet pendingSync = new BitSet();
+
+ public int expectedWifiState;
+ public int expectedP2pState;
+ public int expectedDiscoveryState;
+ public NetworkInfo expectedNetworkInfo;
+ }
+
+ private class MyResponse {
+ public boolean valid = false;
+
+ public boolean success;
+ public int p2pState;
+ public int discoveryState;
+ public NetworkInfo networkInfo;
}
private WifiManager mWifiManager;
+ private WifiP2pManager mWifiP2pManager;
+ private WifiP2pManager.Channel mWifiP2pChannel;
private MySync mMySync = new MySync();
+ private MyResponse mMyResponse = new MyResponse();
- private static final String TAG = "WifiInfoTest";
+ private static final String TAG = "ConcurrencyTest";
private static final int TIMEOUT_MSEC = 6000;
private static final int WAIT_MSEC = 60;
private static final int DURATION = 10000;
@@ -54,16 +88,33 @@
final String action = intent.getAction();
if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
synchronized (mMySync) {
+ mMySync.pendingSync.set(MySync.WIFI_STATE);
mMySync.expectedWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_DISABLED);
mMySync.notify();
}
} else if(action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) {
synchronized (mMySync) {
+ mMySync.pendingSync.set(MySync.P2P_STATE);
mMySync.expectedP2pState = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
WifiP2pManager.WIFI_P2P_STATE_DISABLED);
mMySync.notify();
}
+ } else if (action.equals(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION)) {
+ synchronized (mMySync) {
+ mMySync.pendingSync.set(MySync.DISCOVERY_STATE);
+ mMySync.expectedDiscoveryState = intent.getIntExtra(
+ WifiP2pManager.EXTRA_DISCOVERY_STATE,
+ WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED);
+ mMySync.notify();
+ }
+ } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
+ synchronized (mMySync) {
+ mMySync.pendingSync.set(MySync.NETWORK_INFO);
+ mMySync.expectedNetworkInfo = (NetworkInfo) intent.getExtra(
+ WifiP2pManager.EXTRA_NETWORK_INFO, null);
+ mMySync.notify();
+ }
}
}
};
@@ -79,17 +130,21 @@
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
mContext.registerReceiver(mReceiver, mIntentFilter);
mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
assertNotNull(mWifiManager);
if (mWifiManager.isWifiEnabled()) {
- assertTrue(mWifiManager.setWifiEnabled(false));
+ SystemUtil.runShellCommand("svc wifi disable");
Thread.sleep(DURATION);
}
assertTrue(!mWifiManager.isWifiEnabled());
mMySync.expectedWifiState = WifiManager.WIFI_STATE_DISABLED;
mMySync.expectedP2pState = WifiP2pManager.WIFI_P2P_STATE_DISABLED;
+ mMySync.expectedDiscoveryState = WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED;
+ mMySync.expectedNetworkInfo = null;
}
@Override
@@ -106,16 +161,66 @@
super.tearDown();
}
- private void waitForBroadcasts() {
+ private boolean waitForBroadcasts(List<Integer> waitSyncList) {
synchronized (mMySync) {
long timeout = System.currentTimeMillis() + TIMEOUT_MSEC;
- while (System.currentTimeMillis() < timeout
- && (mMySync.expectedWifiState != WifiManager.WIFI_STATE_ENABLED ||
- mMySync.expectedP2pState != WifiP2pManager.WIFI_P2P_STATE_ENABLED)) {
+ while (System.currentTimeMillis() < timeout) {
+ List<Integer> handledSyncList = waitSyncList.stream()
+ .filter(w -> mMySync.pendingSync.get(w))
+ .collect(Collectors.toList());
+ handledSyncList.forEach(w -> mMySync.pendingSync.clear(w));
+ waitSyncList.removeAll(handledSyncList);
+ if (waitSyncList.isEmpty()) {
+ break;
+ }
try {
mMySync.wait(WAIT_MSEC);
} catch (InterruptedException e) { }
}
+ if (!waitSyncList.isEmpty()) {
+ Log.i(TAG, "Missing broadcast: " + waitSyncList);
+ }
+ return waitSyncList.isEmpty();
+ }
+ }
+
+ private boolean waitForBroadcasts(int waitSingleSync) {
+ return waitForBroadcasts(
+ new LinkedList<Integer>(Arrays.asList(waitSingleSync)));
+ }
+
+ private boolean waitForServiceResponse(MyResponse waitResponse) {
+ synchronized (waitResponse) {
+ long timeout = System.currentTimeMillis() + TIMEOUT_MSEC;
+ while (System.currentTimeMillis() < timeout) {
+ try {
+ waitResponse.wait(WAIT_MSEC);
+ } catch (InterruptedException e) { }
+
+ if (waitResponse.valid) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ // Return true if location is enabled.
+ private boolean isLocationEnabled() {
+ return Settings.Secure.getInt(getContext().getContentResolver(),
+ Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF)
+ != Settings.Secure.LOCATION_MODE_OFF;
+ }
+
+ // Returns true if the device has location feature.
+ private boolean hasLocationFeature() {
+ return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION);
+ }
+
+ private void resetResponse(MyResponse responseObj) {
+ synchronized (responseObj) {
+ responseObj.valid = false;
+ responseObj.networkInfo = null;
}
}
@@ -124,7 +229,7 @@
*/
private void enableWifi() throws InterruptedException {
if (!mWifiManager.isWifiEnabled()) {
- assertTrue(mWifiManager.setWifiEnabled(true));
+ SystemUtil.runShellCommand("svc wifi enable");
}
ConnectivityManager cm =
@@ -146,25 +251,212 @@
cm.unregisterNetworkCallback(networkCallback);
}
- public void testConcurrency() {
+ private boolean setupWifiP2p() {
// Cannot support p2p alone
if (!WifiFeature.isWifiSupported(getContext())) {
assertTrue(!WifiFeature.isP2pSupported(getContext()));
- return;
+ return false;
}
if (!WifiFeature.isP2pSupported(getContext())) {
// skip the test if p2p is not supported
+ return false;
+ }
+
+ if (!hasLocationFeature()) {
+ Log.d(TAG, "Skipping test as location is not supported");
+ return false;
+ }
+ if (!isLocationEnabled()) {
+ fail("Please enable location for this test - since P-release WiFi Direct"
+ + " needs Location enabled.");
+ }
+
+ long timeout = System.currentTimeMillis() + TIMEOUT_MSEC;
+ while (!mWifiManager.isWifiEnabled() && System.currentTimeMillis() < timeout) {
+ try {
+ enableWifi();
+ } catch (InterruptedException e) { }
+ }
+
+ assertTrue(mWifiManager.isWifiEnabled());
+
+ assertTrue(waitForBroadcasts(
+ new LinkedList<Integer>(
+ Arrays.asList(MySync.WIFI_STATE, MySync.P2P_STATE))));
+
+ assertEquals(WifiManager.WIFI_STATE_ENABLED, mMySync.expectedWifiState);
+ assertEquals(WifiP2pManager.WIFI_P2P_STATE_ENABLED, mMySync.expectedP2pState);
+
+ mWifiP2pManager =
+ (WifiP2pManager) getContext().getSystemService(Context.WIFI_P2P_SERVICE);
+ mWifiP2pChannel = mWifiP2pManager.initialize(
+ getContext(), getContext().getMainLooper(), null);
+
+ assertNotNull(mWifiP2pManager);
+ assertNotNull(mWifiP2pChannel);
+
+ assertTrue(waitForBroadcasts(MySync.NETWORK_INFO));
+ // wait for changing to EnabledState
+ assertNotNull(mMySync.expectedNetworkInfo);
+ assertTrue(mMySync.expectedNetworkInfo.isAvailable());
+
+ return true;
+ }
+
+ public void testConcurrency() {
+ if (!setupWifiP2p()) {
return;
}
- // Enable wifi
- assertTrue(mWifiManager.setWifiEnabled(true));
+ resetResponse(mMyResponse);
+ mWifiP2pManager.requestP2pState(mWifiP2pChannel, new WifiP2pManager.P2pStateListener() {
+ @Override
+ public void onP2pStateAvailable(int state) {
+ synchronized (mMyResponse) {
+ mMyResponse.valid = true;
+ mMyResponse.p2pState = state;
+ mMyResponse.notify();
+ }
+ }
+ });
+ assertTrue(waitForServiceResponse(mMyResponse));
+ assertEquals(WifiP2pManager.WIFI_P2P_STATE_ENABLED, mMyResponse.p2pState);
+ }
- waitForBroadcasts();
+ public void testRequestDiscoveryState() {
+ if (!setupWifiP2p()) {
+ return;
+ }
- assertTrue(mMySync.expectedWifiState == WifiManager.WIFI_STATE_ENABLED);
- assertTrue(mMySync.expectedP2pState == WifiP2pManager.WIFI_P2P_STATE_ENABLED);
+ resetResponse(mMyResponse);
+ mWifiP2pManager.requestDiscoveryState(
+ mWifiP2pChannel, new WifiP2pManager.DiscoveryStateListener() {
+ @Override
+ public void onDiscoveryStateAvailable(int state) {
+ synchronized (mMyResponse) {
+ mMyResponse.valid = true;
+ mMyResponse.discoveryState = state;
+ mMyResponse.notify();
+ }
+ }
+ });
+ assertTrue(waitForServiceResponse(mMyResponse));
+ assertEquals(WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED, mMyResponse.discoveryState);
+
+ resetResponse(mMyResponse);
+ mWifiP2pManager.discoverPeers(mWifiP2pChannel, new WifiP2pManager.ActionListener() {
+ @Override
+ public void onSuccess() {
+ synchronized (mMyResponse) {
+ mMyResponse.valid = true;
+ mMyResponse.success = true;
+ mMyResponse.notify();
+ }
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ synchronized (mMyResponse) {
+ Log.d(TAG, "discoveryPeers failure reason: " + reason);
+ mMyResponse.valid = true;
+ mMyResponse.success = false;
+ mMyResponse.notify();
+ }
+ }
+ });
+ assertTrue(waitForServiceResponse(mMyResponse));
+ assertTrue(mMyResponse.success);
+ assertTrue(waitForBroadcasts(MySync.DISCOVERY_STATE));
+
+ resetResponse(mMyResponse);
+ mWifiP2pManager.requestDiscoveryState(mWifiP2pChannel,
+ new WifiP2pManager.DiscoveryStateListener() {
+ @Override
+ public void onDiscoveryStateAvailable(int state) {
+ synchronized (mMyResponse) {
+ mMyResponse.valid = true;
+ mMyResponse.discoveryState = state;
+ mMyResponse.notify();
+ }
+ }
+ });
+ assertTrue(waitForServiceResponse(mMyResponse));
+ assertEquals(WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED, mMyResponse.discoveryState);
+
+ mWifiP2pManager.stopPeerDiscovery(mWifiP2pChannel, null);
+ }
+
+ public void testRequestNetworkInfo() {
+ if (!setupWifiP2p()) {
+ return;
+ }
+
+ resetResponse(mMyResponse);
+ mWifiP2pManager.requestNetworkInfo(mWifiP2pChannel,
+ new WifiP2pManager.NetworkInfoListener() {
+ @Override
+ public void onNetworkInfoAvailable(NetworkInfo info) {
+ synchronized (mMyResponse) {
+ mMyResponse.valid = true;
+ mMyResponse.networkInfo = info;
+ mMyResponse.notify();
+ }
+ }
+ });
+ assertTrue(waitForServiceResponse(mMyResponse));
+ assertNotNull(mMyResponse.networkInfo);
+ // The state might be IDLE, DISCONNECTED, FAILED before a connection establishment.
+ // Just ensure the state is NOT CONNECTED.
+ assertNotEquals(NetworkInfo.DetailedState.CONNECTED,
+ mMySync.expectedNetworkInfo.getDetailedState());
+
+ resetResponse(mMyResponse);
+ mWifiP2pManager.createGroup(mWifiP2pChannel, new WifiP2pManager.ActionListener() {
+ @Override
+ public void onSuccess() {
+ synchronized (mMyResponse) {
+ mMyResponse.valid = true;
+ mMyResponse.success = true;
+ mMyResponse.notify();
+ }
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ synchronized (mMyResponse) {
+ Log.d(TAG, "createGroup failure reason: " + reason);
+ mMyResponse.valid = true;
+ mMyResponse.success = false;
+ mMyResponse.notify();
+ }
+ }
+ });
+ assertTrue(waitForServiceResponse(mMyResponse));
+ assertTrue(mMyResponse.success);
+ assertTrue(waitForBroadcasts(MySync.NETWORK_INFO));
+ assertNotNull(mMySync.expectedNetworkInfo);
+ assertEquals(NetworkInfo.DetailedState.CONNECTED,
+ mMySync.expectedNetworkInfo.getDetailedState());
+
+ resetResponse(mMyResponse);
+ mWifiP2pManager.requestNetworkInfo(mWifiP2pChannel,
+ new WifiP2pManager.NetworkInfoListener() {
+ @Override
+ public void onNetworkInfoAvailable(NetworkInfo info) {
+ synchronized (mMyResponse) {
+ mMyResponse.valid = true;
+ mMyResponse.networkInfo = info;
+ mMyResponse.notify();
+ }
+ }
+ });
+ assertTrue(waitForServiceResponse(mMyResponse));
+ assertNotNull(mMyResponse.networkInfo);
+ assertEquals(NetworkInfo.DetailedState.CONNECTED,
+ mMyResponse.networkInfo.getDetailedState());
+
+ mWifiP2pManager.removeGroup(mWifiP2pChannel, null);
}
}
diff --git a/tests/cts/net/src/android/net/wifi/cts/MulticastLockTest.java b/tests/cts/net/src/android/net/wifi/cts/MulticastLockTest.java
new file mode 100644
index 0000000..71f04a3
--- /dev/null
+++ b/tests/cts/net/src/android/net/wifi/cts/MulticastLockTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.cts;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.MulticastLock;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+public class MulticastLockTest extends AndroidTestCase {
+
+ private static final String WIFI_TAG = "MulticastLockTest";
+
+ /**
+ * Verify acquire and release of Multicast locks
+ */
+ public void testMulticastLock() {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ WifiManager wm = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
+ MulticastLock mcl = wm.createMulticastLock(WIFI_TAG);
+
+ mcl.setReferenceCounted(true);
+ assertFalse(mcl.isHeld());
+ mcl.acquire();
+ assertTrue(mcl.isHeld());
+ mcl.release();
+ assertFalse(mcl.isHeld());
+ mcl.acquire();
+ mcl.acquire();
+ assertTrue(mcl.isHeld());
+ mcl.release();
+ assertTrue(mcl.isHeld());
+ mcl.release();
+ assertFalse(mcl.isHeld());
+ assertNotNull(mcl.toString());
+ try {
+ mcl.release();
+ fail("should throw out exception because release is called"
+ +" a greater number of times than acquire");
+ } catch (RuntimeException e) {
+ // expected
+ }
+
+ mcl = wm.createMulticastLock(WIFI_TAG);
+ mcl.setReferenceCounted(false);
+ assertFalse(mcl.isHeld());
+ mcl.acquire();
+ assertTrue(mcl.isHeld());
+ mcl.release();
+ assertFalse(mcl.isHeld());
+ mcl.acquire();
+ mcl.acquire();
+ assertTrue(mcl.isHeld());
+ mcl.release();
+ assertFalse(mcl.isHeld());
+ assertNotNull(mcl.toString());
+ // releasing again after release: but ignored for non-referenced locks
+ mcl.release();
+ }
+}
diff --git a/tests/cts/net/src/android/net/wifi/cts/NsdManagerTest.java b/tests/cts/net/src/android/net/wifi/cts/NsdManagerTest.java
index 2e2e75b..f2a2b48 100644
--- a/tests/cts/net/src/android/net/wifi/cts/NsdManagerTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/NsdManagerTest.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
import android.util.Log;
@@ -29,6 +30,7 @@
import java.util.List;
import java.util.ArrayList;
+@AppModeFull(reason = "Socket cannot bind in instant app mode")
public class NsdManagerTest extends AndroidTestCase {
private static final String TAG = "NsdManagerTest";
diff --git a/tests/cts/net/src/android/net/wifi/cts/ScanResultTest.java b/tests/cts/net/src/android/net/wifi/cts/ScanResultTest.java
index 8a22bef..ccf5fe2 100644
--- a/tests/cts/net/src/android/net/wifi/cts/ScanResultTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/ScanResultTest.java
@@ -25,9 +25,13 @@
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
+import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
import android.util.Log;
+import com.android.compatibility.common.util.SystemUtil;
+
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public class ScanResultTest extends AndroidTestCase {
private static class MySync {
int expectedState = STATE_NULL;
@@ -121,7 +125,11 @@
private void setWifiEnabled(boolean enable) throws Exception {
synchronized (mMySync) {
mMySync.expectedState = STATE_WIFI_CHANGING;
- assertTrue(mWifiManager.setWifiEnabled(enable));
+ if (enable) {
+ SystemUtil.runShellCommand("svc wifi enable");
+ } else {
+ SystemUtil.runShellCommand("svc wifi disable");
+ }
waitForBroadcast(TIMEOUT_MSEC, STATE_WIFI_CHANGED);
}
}
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiConfigurationTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiConfigurationTest.java
index 4480a24..a59c85e 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiConfigurationTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiConfigurationTest.java
@@ -21,8 +21,10 @@
import android.content.Context;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
+import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public class WifiConfigurationTest extends AndroidTestCase {
private WifiManager mWifiManager;
@Override
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
index d3235da..b592c10 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
@@ -18,13 +18,15 @@
import android.content.Context;
import android.content.pm.PackageManager;
-import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiEnterpriseConfig.Eap;
import android.net.wifi.WifiEnterpriseConfig.Phase2;
import android.net.wifi.WifiManager;
+import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
+import com.android.compatibility.common.util.SystemUtil;
+
import java.io.ByteArrayInputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
@@ -32,8 +34,9 @@
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public class WifiEnterpriseConfigTest extends AndroidTestCase {
- private WifiManager mWifiManager;
+ private WifiManager mWifiManager;
private static final String SSID = "\"TestSSID\"";
private static final String IDENTITY = "identity";
@@ -687,7 +690,7 @@
mWifiManager = (WifiManager) mContext
.getSystemService(Context.WIFI_SERVICE);
assertNotNull(mWifiManager);
- mWifiManager.setWifiEnabled(true);
+ SystemUtil.runShellCommand("svc wifi enable");
Thread.sleep(ENABLE_DELAY);
if (hasWifi()) {
assertTrue(mWifiManager.isWifiEnabled());
@@ -774,33 +777,6 @@
assertTrue(config.getDomainSuffixMatch().equals(DOM_SUBJECT_MATCH));
}
- public void testAddEapNetwork() {
- if (!hasWifi()) {
- return;
- }
-
- WifiConfiguration config = new WifiConfiguration();
- WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
- enterpriseConfig.setEapMethod(Eap.PWD);
- enterpriseConfig.setIdentity(IDENTITY);
- enterpriseConfig.setPassword(PASSWORD);
- config.SSID = SSID;
- config.enterpriseConfig = enterpriseConfig;
-
- int netId = mWifiManager.addNetwork(config);
- assertTrue(doesSsidExist(SSID));
- mWifiManager.removeNetwork(netId);
- assertFalse(doesSsidExist(SSID));
- }
-
- private boolean doesSsidExist(String ssid) {
- for (final WifiConfiguration w : mWifiManager.getConfiguredNetworks()) {
- if (w.SSID.equals(ssid))
- return true;
- }
- return false;
- }
-
public void testEnterpriseConfigDoesNotPrintPassword() {
WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
final String identity = "IdentityIsOkayToBeDisplayedHere";
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiInfoTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiInfoTest.java
index 5983cb7..5367722 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiInfoTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiInfoTest.java
@@ -26,12 +26,15 @@
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
import android.net.wifi.WifiSsid;
+import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
import java.util.concurrent.Callable;
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public class WifiInfoTest extends AndroidTestCase {
private static class MySync {
int expectedState = STATE_NULL;
@@ -104,7 +107,11 @@
private void setWifiEnabled(boolean enable) throws Exception {
synchronized (mMySync) {
mMySync.expectedState = STATE_WIFI_CHANGING;
- assertTrue(mWifiManager.setWifiEnabled(enable));
+ if (enable) {
+ SystemUtil.runShellCommand("svc wifi enable");
+ } else {
+ SystemUtil.runShellCommand("svc wifi disable");
+ }
long timeout = System.currentTimeMillis() + TIMEOUT_MSEC;
while (System.currentTimeMillis() < timeout
&& mMySync.expectedState == STATE_WIFI_CHANGING)
@@ -134,6 +141,8 @@
wifiInfo.getBSSID();
wifiInfo.getIpAddress();
wifiInfo.getLinkSpeed();
+ wifiInfo.getTxLinkSpeedMbps();
+ wifiInfo.getRxLinkSpeedMbps();
wifiInfo.getRssi();
wifiInfo.getHiddenSSID();
wifiInfo.getMacAddress();
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiManager_WifiLockTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiLockTest.java
similarity index 71%
rename from tests/cts/net/src/android/net/wifi/cts/WifiManager_WifiLockTest.java
rename to tests/cts/net/src/android/net/wifi/cts/WifiLockTest.java
index 3cdd56a..6ac92d4 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiManager_WifiLockTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiLockTest.java
@@ -19,19 +19,35 @@
import android.content.Context;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
+import android.platform.test.annotations.AppModeFull;
import android.test.AndroidTestCase;
-public class WifiManager_WifiLockTest extends AndroidTestCase {
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+public class WifiLockTest extends AndroidTestCase {
- private static final String WIFI_TAG = "WifiManager_WifiLockTest";
+ private static final String WIFI_TAG = "WifiLockTest";
- public void testWifiLock() {
+ /**
+ * Verify acquire and release of High Performance wifi locks
+ */
+ public void testHiPerfWifiLock() {
+ testWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF);
+ }
+
+ /**
+ * Verify acquire and release of Low latency wifi locks
+ */
+ public void testLowLatencyWifiLock() {
+ testWifiLock(WifiManager.WIFI_MODE_FULL_LOW_LATENCY);
+ }
+
+ private void testWifiLock(int lockType) {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
WifiManager wm = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
- WifiLock wl = wm.createWifiLock(WIFI_TAG);
+ WifiLock wl = wm.createWifiLock(lockType, WIFI_TAG);
wl.setReferenceCounted(true);
assertFalse(wl.isHeld());
@@ -55,7 +71,7 @@
// expected
}
- wl = wm.createWifiLock(WIFI_TAG);
+ wl = wm.createWifiLock(lockType, WIFI_TAG);
wl.setReferenceCounted(false);
assertFalse(wl.isHeld());
wl.acquire();
@@ -68,7 +84,7 @@
wl.release();
assertFalse(wl.isHeld());
assertNotNull(wl.toString());
- // should be ignored
+ // releasing again after release: but ignored for non-referenced locks
wl.release();
}
}
diff --git a/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java b/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
index 2ed0124..0ef5cd9 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
@@ -21,34 +21,45 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.Status;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.TxPacketCountListener;
import android.net.wifi.WifiManager.WifiLock;
import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSp;
+import android.os.Process;
import android.os.SystemClock;
+import android.os.UserHandle;
+import android.platform.test.annotations.AppModeFull;
import android.provider.Settings;
+import android.support.test.uiautomator.UiDevice;
import android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.util.ArraySet;
import android.util.Log;
-import com.android.compatibility.common.util.WifiConfigCreator;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.cert.X509Certificate;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
+import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public class WifiManagerTest extends AndroidTestCase {
private static class MySync {
int expectedState = STATE_NULL;
@@ -60,6 +71,7 @@
private List<ScanResult> mScanResults = null;
private NetworkInfo mNetworkInfo;
private Object mLOHSLock = new Object();
+ private UiDevice mUiDevice;
// Please refer to WifiManager
private static final int MIN_RSSI = -100;
@@ -74,9 +86,6 @@
private static final String TAG = "WifiManagerTest";
private static final String SSID1 = "\"WifiManagerTest\"";
- private static final String SSID2 = "\"WifiManagerTestModified\"";
- private static final String PROXY_TEST_SSID = "SomeProxyAp";
- private static final String ADD_NETWORK_EXCEPTION_SUBSTR = "addNetwork";
// A full single scan duration is about 6-7 seconds if country code is set
// to US. If country code is set to world mode (00), we would expect a scan
// duration of roughly 8 seconds. So we set scan timeout as 9 seconds here.
@@ -84,11 +93,16 @@
private static final int TIMEOUT_MSEC = 6000;
private static final int WAIT_MSEC = 60;
private static final int DURATION = 10000;
+ private static final int DURATION_SCREEN_TOGGLE = 2000;
private static final int WIFI_SCAN_TEST_INTERVAL_MILLIS = 60 * 1000;
private static final int WIFI_SCAN_TEST_CACHE_DELAY_MILLIS = 3 * 60 * 1000;
private static final int WIFI_SCAN_TEST_ITERATIONS = 5;
+ private static final int ENFORCED_NUM_NETWORK_SUGGESTIONS_PER_APP = 50;
+
private static final String TEST_PAC_URL = "http://www.example.com/proxy.pac";
+ private static final String MANAGED_PROVISIONING_PACKAGE_NAME
+ = "com.android.managedprovisioning";
private IntentFilter mIntentFilter;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -156,6 +170,8 @@
mWifiLock.acquire();
if (!mWifiManager.isWifiEnabled())
setWifiEnabled(true);
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ turnScreenOnNoDelay();
Thread.sleep(DURATION);
assertTrue(mWifiManager.isWifiEnabled());
synchronized (mMySync) {
@@ -186,8 +202,8 @@
} else {
mMySync.expectedState = (enable ? STATE_WIFI_ENABLED : STATE_WIFI_DISABLED);
}
- // now trigger the change
- assertTrue(mWifiManager.setWifiEnabled(enable));
+ // now trigger the change using shell commands.
+ SystemUtil.runShellCommand("svc wifi " + (enable ? "enable" : "disable"));
waitForExpectedWifiState(enable);
}
}
@@ -236,7 +252,6 @@
private void connectWifi() throws Exception {
synchronized (mMySync) {
if (mNetworkInfo.getState() == NetworkInfo.State.CONNECTED) return;
- assertTrue(mWifiManager.reconnect());
long timeout = System.currentTimeMillis() + TIMEOUT_MSEC;
while (System.currentTimeMillis() < timeout
&& mNetworkInfo.getState() != NetworkInfo.State.CONNECTED)
@@ -262,20 +277,13 @@
}
/**
- * test point of wifiManager actions:
- * 1.reconnect
- * 2.reassociate
- * 3.disconnect
- * 4.createWifiLock
+ * Test creation of WifiManager Lock.
*/
- public void testWifiManagerActions() throws Exception {
+ public void testWifiManagerLock() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
- assertTrue(mWifiManager.reconnect());
- assertTrue(mWifiManager.reassociate());
- assertTrue(mWifiManager.disconnect());
final String TAG = "Test";
assertNotNull(mWifiManager.createWifiLock(TAG));
assertNotNull(mWifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG));
@@ -396,123 +404,8 @@
return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION);
}
- /**
- * test point of wifiManager NetWork:
- * 1.add NetWork
- * 2.update NetWork
- * 3.remove NetWork
- * 4.enable NetWork
- * 5.disable NetWork
- * 6.configured Networks
- * 7.save configure;
- */
- public void testWifiManagerNetWork() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
-
- // store the list of enabled networks, so they can be re-enabled after test completes
- Set<String> enabledSsids = getEnabledNetworks(mWifiManager.getConfiguredNetworks());
- try {
- WifiConfiguration wifiConfiguration;
- // add a WifiConfig
- final int notExist = -1;
- List<WifiConfiguration> wifiConfiguredNetworks = mWifiManager.getConfiguredNetworks();
- int pos = findConfiguredNetworks(SSID1, wifiConfiguredNetworks);
- if (notExist != pos) {
- wifiConfiguration = wifiConfiguredNetworks.get(pos);
- mWifiManager.removeNetwork(wifiConfiguration.networkId);
- }
- pos = findConfiguredNetworks(SSID1, wifiConfiguredNetworks);
- assertEquals(notExist, pos);
- final int size = wifiConfiguredNetworks.size();
-
- wifiConfiguration = new WifiConfiguration();
- wifiConfiguration.SSID = SSID1;
- wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
- int netId = mWifiManager.addNetwork(wifiConfiguration);
- assertTrue(existSSID(SSID1));
-
- wifiConfiguredNetworks = mWifiManager.getConfiguredNetworks();
- assertEquals(size + 1, wifiConfiguredNetworks.size());
- pos = findConfiguredNetworks(SSID1, wifiConfiguredNetworks);
- assertTrue(notExist != pos);
-
- // Enable & disable network
- boolean disableOthers = true;
- assertTrue(mWifiManager.enableNetwork(netId, disableOthers));
- wifiConfiguration = mWifiManager.getConfiguredNetworks().get(pos);
- assertEquals(Status.ENABLED, wifiConfiguration.status);
-
- assertTrue(mWifiManager.disableNetwork(netId));
- wifiConfiguration = mWifiManager.getConfiguredNetworks().get(pos);
- assertEquals(Status.DISABLED, wifiConfiguration.status);
-
- // Update a WifiConfig
- wifiConfiguration = wifiConfiguredNetworks.get(pos);
- wifiConfiguration.SSID = SSID2;
- netId = mWifiManager.updateNetwork(wifiConfiguration);
- assertFalse(existSSID(SSID1));
- assertTrue(existSSID(SSID2));
-
- // Remove a WifiConfig
- assertTrue(mWifiManager.removeNetwork(netId));
- assertFalse(mWifiManager.removeNetwork(notExist));
- assertFalse(existSSID(SSID1));
- assertFalse(existSSID(SSID2));
-
- assertTrue(mWifiManager.saveConfiguration());
- } finally {
- reEnableNetworks(enabledSsids, mWifiManager.getConfiguredNetworks());
- mWifiManager.saveConfiguration();
- }
- }
-
- /**
- * Verifies that addNetwork() fails for WifiConfigurations containing a non-null http proxy when
- * the caller doesn't have OVERRIDE_WIFI_CONFIG permission, DeviceOwner or ProfileOwner device
- * management policies
- */
- public void testSetHttpProxy_PermissionFail() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
- WifiConfigCreator configCreator = new WifiConfigCreator(getContext());
- boolean exceptionThrown = false;
- try {
- configCreator.addHttpProxyNetworkVerifyAndRemove(
- PROXY_TEST_SSID, TEST_PAC_URL);
- } catch (IllegalStateException e) {
- // addHttpProxyNetworkVerifyAndRemove throws three IllegalStateException,
- // expect it to throw for the addNetwork operation
- if (e.getMessage().contains(ADD_NETWORK_EXCEPTION_SUBSTR)) {
- exceptionThrown = true;
- }
- }
- assertTrue(exceptionThrown);
- }
-
- private Set<String> getEnabledNetworks(List<WifiConfiguration> configuredNetworks) {
- Set<String> ssids = new HashSet<String>();
- for (WifiConfiguration wifiConfig : configuredNetworks) {
- if (Status.ENABLED == wifiConfig.status || Status.CURRENT == wifiConfig.status) {
- ssids.add(wifiConfig.SSID);
- Log.i(TAG, String.format("remembering enabled network %s", wifiConfig.SSID));
- }
- }
- return ssids;
- }
-
- private void reEnableNetworks(Set<String> enabledSsids,
- List<WifiConfiguration> configuredNetworks) {
- for (WifiConfiguration wifiConfig : configuredNetworks) {
- if (enabledSsids.contains(wifiConfig.SSID)) {
- mWifiManager.enableNetwork(wifiConfig.networkId, false);
- Log.i(TAG, String.format("re-enabling network %s", wifiConfig.SSID));
- }
- }
+ private boolean hasAutomotiveFeature() {
+ return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
}
public void testSignal() {
@@ -621,164 +514,6 @@
assertTrue(i < 15);
}
- /**
- * Verify Passpoint configuration management APIs (add, remove, get) for a Passpoint
- * configuration with an user credential.
- *
- * @throws Exception
- */
- public void testAddPasspointConfigWithUserCredential() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
- testAddPasspointConfig(generatePasspointConfig(generateUserCredential()));
- }
-
- /**
- * Verify Passpoint configuration management APIs (add, remove, get) for a Passpoint
- * configuration with a certificate credential.
- *
- * @throws Exception
- */
- public void testAddPasspointConfigWithCertCredential() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
- testAddPasspointConfig(generatePasspointConfig(generateCertCredential()));
- }
-
- /**
- * Verify Passpoint configuration management APIs (add, remove, get) for a Passpoint
- * configuration with a SIm credential.
- *
- * @throws Exception
- */
- public void testAddPasspointConfigWithSimCredential() throws Exception {
- if (!WifiFeature.isWifiSupported(getContext())) {
- // skip the test if WiFi is not supported
- return;
- }
- testAddPasspointConfig(generatePasspointConfig(generateSimCredential()));
- }
-
- /**
- * Helper function for generating a {@link PasspointConfiguration} for testing.
- *
- * @return {@link PasspointConfiguration}
- */
- private PasspointConfiguration generatePasspointConfig(Credential credential) {
- PasspointConfiguration config = new PasspointConfiguration();
- config.setCredential(credential);
-
- // Setup HomeSp.
- HomeSp homeSp = new HomeSp();
- homeSp.setFqdn("Test.com");
- homeSp.setFriendlyName("Test Provider");
- homeSp.setRoamingConsortiumOis(new long[] {0x11223344});
- config.setHomeSp(homeSp);
-
- return config;
- }
-
- /**
- * Helper function for generating an user credential for testing.
- *
- * @return {@link Credential}
- */
- private Credential generateUserCredential() {
- Credential credential = new Credential();
- credential.setRealm("test.net");
- Credential.UserCredential userCred = new Credential.UserCredential();
- userCred.setEapType(21 /* EAP_TTLS */);
- userCred.setUsername("username");
- userCred.setPassword("password");
- userCred.setNonEapInnerMethod("PAP");
- credential.setUserCredential(userCred);
- credential.setCaCertificate(FakeKeys.CA_PUBLIC_CERT);
- return credential;
- }
-
- /**
- * Helper function for generating a certificate credential for testing.
- *
- * @return {@link Credential}
- */
- private Credential generateCertCredential() throws Exception {
- Credential credential = new Credential();
- credential.setRealm("test.net");
- Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
- certCredential.setCertType("x509v3");
- certCredential.setCertSha256Fingerprint(
- MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded()));
- credential.setCertCredential(certCredential);
- credential.setCaCertificate(FakeKeys.CA_PUBLIC_CERT);
- credential.setClientCertificateChain(new X509Certificate[] {FakeKeys.CLIENT_CERT});
- credential.setClientPrivateKey(FakeKeys.RSA_KEY1);
- return credential;
- }
-
- /**
- * Helper function for generating a SIM credential for testing.
- *
- * @return {@link Credential}
- */
- private Credential generateSimCredential() throws Exception {
- Credential credential = new Credential();
- credential.setRealm("test.net");
- Credential.SimCredential simCredential = new Credential.SimCredential();
- simCredential.setImsi("1234*");
- simCredential.setEapType(18 /* EAP_SIM */);
- credential.setSimCredential(simCredential);
- return credential;
- }
-
- /**
- * Helper function verifying Passpoint configuration management APIs (add, remove, get) for
- * a given configuration.
- *
- * @param config The configuration to test with
- */
- private void testAddPasspointConfig(PasspointConfiguration config) throws Exception {
- try {
-
- // obtain number of passpoint networks already present in device (preloaded)
- List<PasspointConfiguration> preConfigList = mWifiManager.getPasspointConfigurations();
- int numOfNetworks = preConfigList.size();
-
- // add new (test) configuration
- mWifiManager.addOrUpdatePasspointConfiguration(config);
-
- // Certificates and keys will be set to null after it is installed to the KeyStore by
- // WifiManager. Reset them in the expected config so that it can be used to compare
- // against the retrieved config.
- config.getCredential().setCaCertificate(null);
- config.getCredential().setClientCertificateChain(null);
- config.getCredential().setClientPrivateKey(null);
-
- // retrieve the configuration and verify it. The retrieved list may not be in order -
- // check all configs to see if any match
- List<PasspointConfiguration> configList = mWifiManager.getPasspointConfigurations();
- assertEquals(numOfNetworks + 1, configList.size());
-
- boolean anyMatch = false;
- for (PasspointConfiguration passpointConfiguration : configList) {
- if (passpointConfiguration.equals(config)) {
- anyMatch = true;
- break;
- }
- }
- assertTrue(anyMatch);
-
- // remove the (test) configuration and verify number of installed configurations
- mWifiManager.removePasspointConfiguration(config.getHomeSp().getFqdn());
- assertEquals(mWifiManager.getPasspointConfigurations().size(), numOfNetworks);
- } catch (UnsupportedOperationException e) {
- // Passpoint build config |config_wifi_hotspot2_enabled| is disabled, so noop.
- }
- }
-
public class TestLocalOnlyHotspotCallback extends WifiManager.LocalOnlyHotspotCallback {
Object hotspotLock;
WifiManager.LocalOnlyHotspotReservation reservation = null;
@@ -835,6 +570,11 @@
// check if we got the callback
assertTrue(callback.onStartedCalled);
assertNotNull(callback.reservation.getWifiConfiguration());
+ if (!hasAutomotiveFeature()) {
+ assertEquals(
+ WifiConfiguration.AP_BAND_2GHZ,
+ callback.reservation.getWifiConfiguration().apBand);
+ }
assertFalse(callback.onFailedCalled);
assertFalse(callback.onStoppedCalled);
}
@@ -890,37 +630,37 @@
}
/**
- * Verify calls to setWifiEnabled from a non-settings app while softap mode is active do not
- * exit softap mode.
- *
- * This test uses the LocalOnlyHotspot API to enter softap mode. This should also be true when
- * tethering is started.
- * Note: Location mode must be enabled for this test.
+ * Verify calls to deprecated API's all fail for non-settings apps targeting >= Q SDK.
*/
- public void testSetWifiEnabledByAppDoesNotStopHotspot() throws Exception {
+ public void testDeprecatedApis() throws Exception {
if (!WifiFeature.isWifiSupported(getContext())) {
// skip the test if WiFi is not supported
return;
}
- // check that softap mode is supported by the device
- if (!mWifiManager.isPortableHotspotSupported()) {
- return;
- }
+ setWifiEnabled(true);
+ connectWifi(); // ensures that there is at-least 1 saved network on the device.
+
+ WifiConfiguration wifiConfiguration = new WifiConfiguration();
+ wifiConfiguration.SSID = SSID1;
+ wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+
+ assertEquals(WifiConfiguration.INVALID_NETWORK_ID,
+ mWifiManager.addNetwork(wifiConfiguration));
+ assertEquals(WifiConfiguration.INVALID_NETWORK_ID,
+ mWifiManager.updateNetwork(wifiConfiguration));
+ assertFalse(mWifiManager.enableNetwork(0, true));
+ assertFalse(mWifiManager.disableNetwork(0));
+ assertFalse(mWifiManager.removeNetwork(0));
+ assertFalse(mWifiManager.disconnect());
+ assertFalse(mWifiManager.reconnect());
+ assertFalse(mWifiManager.reassociate());
+ assertTrue(mWifiManager.getConfiguredNetworks().isEmpty());
boolean wifiEnabled = mWifiManager.isWifiEnabled();
-
- if (wifiEnabled) {
- // disable wifi so we have something to turn on (some devices may be able to run
- // simultaneous modes)
- setWifiEnabled(false);
- }
-
- TestLocalOnlyHotspotCallback callback = startLocalOnlyHotspot();
-
- // now we should fail to turn on wifi
- assertFalse(mWifiManager.setWifiEnabled(true));
-
- stopLocalOnlyHotspot(callback, wifiEnabled);
+ // now we should fail to toggle wifi state.
+ assertFalse(mWifiManager.setWifiEnabled(!wifiEnabled));
+ Thread.sleep(DURATION);
+ assertEquals(wifiEnabled, mWifiManager.isWifiEnabled());
}
/**
@@ -969,4 +709,343 @@
stopLocalOnlyHotspot(callback, wifiEnabled);
}
+
+ /**
+ * Verify that the {@link android.Manifest.permission#NETWORK_STACK} permission is never held by
+ * any package.
+ * <p>
+ * No apps should <em>ever</em> attempt to acquire this permission, since it would give those
+ * apps extremely broad access to connectivity functionality.
+ */
+ public void testNetworkStackPermission() {
+ final PackageManager pm = getContext().getPackageManager();
+
+ final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
+ android.Manifest.permission.NETWORK_STACK
+ }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+ for (PackageInfo pi : holding) {
+ fail("The NETWORK_STACK permission must not be held by " + pi.packageName
+ + " and must be revoked for security reasons");
+ }
+ }
+
+ /**
+ * Verify that the {@link android.Manifest.permission#NETWORK_SETTINGS} permission is
+ * never held by any package.
+ * <p>
+ * Only Settings, SysUi, NetworkStack and shell apps should <em>ever</em> attempt to acquire
+ * this permission, since it would give those apps extremely broad access to connectivity
+ * functionality. The permission is intended to be granted to only those apps with direct user
+ * access and no others.
+ */
+ public void testNetworkSettingsPermission() {
+ final PackageManager pm = getContext().getPackageManager();
+
+ final ArraySet<String> allowedPackages = new ArraySet();
+ final ArraySet<Integer> allowedUIDs = new ArraySet();
+ // explicitly add allowed UIDs
+ allowedUIDs.add(Process.SYSTEM_UID);
+ allowedUIDs.add(Process.SHELL_UID);
+ allowedUIDs.add(Process.PHONE_UID);
+ allowedUIDs.add(Process.NETWORK_STACK_UID);
+ allowedUIDs.add(Process.NFC_UID);
+
+ // only quick settings is allowed to bind to the BIND_QUICK_SETTINGS_TILE permission, using
+ // this fact to determined allowed package name for sysui. This is a signature permission,
+ // so allow any package with this permission.
+ final List<PackageInfo> sysuiPackages = pm.getPackagesHoldingPermissions(new String[] {
+ android.Manifest.permission.BIND_QUICK_SETTINGS_TILE
+ }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+ for (PackageInfo info : sysuiPackages) {
+ allowedPackages.add(info.packageName);
+ }
+
+ // the captive portal flow also currently holds the NETWORK_SETTINGS permission
+ final Intent intent = new Intent(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
+ final ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DISABLED_COMPONENTS);
+ if (ri != null) {
+ allowedPackages.add(ri.activityInfo.packageName);
+ }
+
+ final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
+ android.Manifest.permission.NETWORK_SETTINGS
+ }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+ for (PackageInfo pi : holding) {
+ String packageName = pi.packageName;
+
+ // this is an explicitly allowed package
+ if (allowedPackages.contains(packageName)) continue;
+
+ // now check if the packages are from allowed UIDs
+ int uid = -1;
+ try {
+ uid = pm.getPackageUidAsUser(packageName, UserHandle.USER_SYSTEM);
+ } catch (PackageManager.NameNotFoundException e) {
+ continue;
+ }
+ if (!allowedUIDs.contains(uid)) {
+ fail("The NETWORK_SETTINGS permission must not be held by " + packageName
+ + ":" + uid + " and must be revoked for security reasons");
+ }
+ }
+ }
+
+ /**
+ * Verify that the {@link android.Manifest.permission#NETWORK_SETUP_WIZARD} permission is
+ * only held by the device setup wizard application.
+ * <p>
+ * Only the SetupWizard app should <em>ever</em> attempt to acquire this
+ * permission, since it would give those apps extremely broad access to connectivity
+ * functionality. The permission is intended to be granted to only the device setup wizard.
+ */
+ public void testNetworkSetupWizardPermission() {
+ final ArraySet<String> allowedPackages = new ArraySet();
+
+ final PackageManager pm = getContext().getPackageManager();
+
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_SETUP_WIZARD);
+ final ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DISABLED_COMPONENTS);
+ String validPkg = "";
+ if (ri != null) {
+ allowedPackages.add(ri.activityInfo.packageName);
+ validPkg = ri.activityInfo.packageName;
+ }
+
+ final Intent preIntent = new Intent("com.android.setupwizard.OEM_PRE_SETUP");
+ preIntent.addCategory(Intent.CATEGORY_DEFAULT);
+ final ResolveInfo preRi = pm
+ .resolveActivity(preIntent, PackageManager.MATCH_DISABLED_COMPONENTS);
+ String prePackageName = "";
+ if (null != preRi) {
+ prePackageName = preRi.activityInfo.packageName;
+ }
+
+ final Intent postIntent = new Intent("com.android.setupwizard.OEM_POST_SETUP");
+ postIntent.addCategory(Intent.CATEGORY_DEFAULT);
+ final ResolveInfo postRi = pm
+ .resolveActivity(postIntent, PackageManager.MATCH_DISABLED_COMPONENTS);
+ String postPackageName = "";
+ if (null != postRi) {
+ postPackageName = postRi.activityInfo.packageName;
+ }
+ if (!TextUtils.isEmpty(prePackageName) && !TextUtils.isEmpty(postPackageName)
+ && prePackageName.equals(postPackageName)) {
+ allowedPackages.add(prePackageName);
+ }
+
+ final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[]{
+ android.Manifest.permission.NETWORK_SETUP_WIZARD
+ }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+ for (PackageInfo pi : holding) {
+ if (!allowedPackages.contains(pi.packageName)) {
+ fail("The NETWORK_SETUP_WIZARD permission must not be held by " + pi.packageName
+ + " and must be revoked for security reasons [" + validPkg + "]");
+ }
+ }
+ }
+
+ /**
+ * Verify that the {@link android.Manifest.permission#NETWORK_MANAGED_PROVISIONING} permission
+ * is only held by the device managed provisioning application.
+ * <p>
+ * Only the ManagedProvisioning app should <em>ever</em> attempt to acquire this
+ * permission, since it would give those apps extremely broad access to connectivity
+ * functionality. The permission is intended to be granted to only the device managed
+ * provisioning.
+ */
+ public void testNetworkManagedProvisioningPermission() {
+ final PackageManager pm = getContext().getPackageManager();
+
+ // TODO(b/115980767): Using hardcoded package name. Need a better mechanism to find the
+ // managed provisioning app.
+ // Ensure that the package exists.
+ final Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setPackage(MANAGED_PROVISIONING_PACKAGE_NAME);
+ final ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DISABLED_COMPONENTS);
+ String validPkg = "";
+ if (ri != null) {
+ validPkg = ri.activityInfo.packageName;
+ }
+
+ final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
+ android.Manifest.permission.NETWORK_MANAGED_PROVISIONING
+ }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+ for (PackageInfo pi : holding) {
+ if (!Objects.equals(pi.packageName, validPkg)) {
+ fail("The NETWORK_MANAGED_PROVISIONING permission must not be held by "
+ + pi.packageName + " and must be revoked for security reasons ["
+ + validPkg +"]");
+ }
+ }
+ }
+
+ /**
+ * Verify that the {@link android.Manifest.permission#WIFI_SET_DEVICE_MOBILITY_STATE} permission
+ * is held by at most one application.
+ */
+ public void testWifiSetDeviceMobilityStatePermission() {
+ final PackageManager pm = getContext().getPackageManager();
+
+ final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
+ android.Manifest.permission.WIFI_SET_DEVICE_MOBILITY_STATE
+ }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+
+ List<String> uniquePackageNames = holding
+ .stream()
+ .map(pi -> pi.packageName)
+ .distinct()
+ .collect(Collectors.toList());
+
+ if (uniquePackageNames.size() > 1) {
+ fail("The WIFI_SET_DEVICE_MOBILITY_STATE permission must not be held by more than one "
+ + "application, but is held by " + uniquePackageNames.size() + " applications: "
+ + String.join(", ", uniquePackageNames));
+ }
+ }
+
+ /**
+ * Verify that the {@link android.Manifest.permission#NETWORK_CARRIER_PROVISIONING} permission
+ * is held by at most one application.
+ */
+ public void testNetworkCarrierProvisioningPermission() {
+ final PackageManager pm = getContext().getPackageManager();
+
+ final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
+ android.Manifest.permission.NETWORK_CARRIER_PROVISIONING
+ }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+
+ List<String> uniquePackageNames = holding
+ .stream()
+ .map(pi -> pi.packageName)
+ .distinct()
+ .collect(Collectors.toList());
+
+ if (uniquePackageNames.size() > 1) {
+ fail("The NETWORK_CARRIER_PROVISIONING permission must not be held by more than one "
+ + "application, but is held by " + uniquePackageNames.size() + " applications: "
+ + String.join(", ", uniquePackageNames));
+ }
+ }
+
+ /**
+ * Verify that the {@link android.Manifest.permission#WIFI_UPDATE_USABILITY_STATS_SCORE}
+ * permission is held by at most one application.
+ */
+ public void testUpdateWifiUsabilityStatsScorePermission() {
+ final PackageManager pm = getContext().getPackageManager();
+
+ final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
+ android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE
+ }, PackageManager.MATCH_UNINSTALLED_PACKAGES);
+
+ List<String> uniquePackageNames = holding
+ .stream()
+ .map(pi -> pi.packageName)
+ .distinct()
+ .collect(Collectors.toList());
+
+ if (uniquePackageNames.size() > 1) {
+ fail("The WIFI_UPDATE_USABILITY_STATS_SCORE permission must not be held by more than "
+ + "one application, but is held by " + uniquePackageNames.size() + " applications: "
+ + String.join(", ", uniquePackageNames));
+ }
+ }
+
+ private void turnScreenOnNoDelay() throws Exception {
+ mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ mUiDevice.executeShellCommand("wm dismiss-keyguard");
+ }
+
+ private void turnScreenOn() throws Exception {
+ turnScreenOnNoDelay();
+ // Since the screen on/off intent is ordered, they will not be sent right now.
+ Thread.sleep(DURATION_SCREEN_TOGGLE);
+ }
+
+ private void turnScreenOff() throws Exception {
+ mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
+ // Since the screen on/off intent is ordered, they will not be sent right now.
+ Thread.sleep(DURATION_SCREEN_TOGGLE);
+ }
+
+ /**
+ * Verify that Wi-Fi scanning is not turned off when the screen turns off while wifi is disabled
+ * but location is on.
+ * @throws Exception
+ */
+ public void testScreenOffDoesNotTurnOffWifiScanningWhenWifiDisabled() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ if (!hasLocationFeature()) {
+ // skip the test if location is not supported
+ return;
+ }
+ if (!isLocationEnabled()) {
+ fail("Please enable location for this test - since Marshmallow WiFi scan results are"
+ + " empty when location is disabled!");
+ }
+ if(!mWifiManager.isScanAlwaysAvailable()) {
+ fail("Please enable Wi-Fi scanning for this test!");
+ }
+ setWifiEnabled(false);
+ turnScreenOn();
+ assertWifiScanningIsOn();
+ // Toggle screen and verify Wi-Fi scanning is still on.
+ turnScreenOff();
+ assertWifiScanningIsOn();
+ turnScreenOn();
+ assertWifiScanningIsOn();
+ }
+
+ /**
+ * Verify that Wi-Fi scanning is not turned off when the screen turns off while wifi is enabled.
+ * @throws Exception
+ */
+ public void testScreenOffDoesNotTurnOffWifiScanningWhenWifiEnabled() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ if (!hasLocationFeature()) {
+ // skip the test if location is not supported
+ return;
+ }
+ if (!isLocationEnabled()) {
+ fail("Please enable location for this test - since Marshmallow WiFi scan results are"
+ + " empty when location is disabled!");
+ }
+ if(!mWifiManager.isScanAlwaysAvailable()) {
+ fail("Please enable Wi-Fi scanning for this test!");
+ }
+ setWifiEnabled(true);
+ turnScreenOn();
+ assertWifiScanningIsOn();
+ // Toggle screen and verify Wi-Fi scanning is still on.
+ turnScreenOff();
+ assertWifiScanningIsOn();
+ turnScreenOn();
+ assertWifiScanningIsOn();
+ }
+
+ /**
+ * Verify that the platform supports a reasonable number of suggestions per app.
+ * @throws Exception
+ */
+ public void testMaxNumberOfNetworkSuggestionsPerApp() throws Exception {
+ if (!WifiFeature.isWifiSupported(getContext())) {
+ // skip the test if WiFi is not supported
+ return;
+ }
+ assertTrue(mWifiManager.getMaxNumberOfNetworkSuggestionsPerApp()
+ > ENFORCED_NUM_NETWORK_SUGGESTIONS_PER_APP);
+ }
+
+ private void assertWifiScanningIsOn() {
+ if(!mWifiManager.isScanAlwaysAvailable()) {
+ fail("Wi-Fi scanning should be on.");
+ }
+ }
}
diff --git a/tests/cts/net/src/android/net/wifi/p2p/cts/WifiP2pConfigTest.java b/tests/cts/net/src/android/net/wifi/p2p/cts/WifiP2pConfigTest.java
new file mode 100644
index 0000000..639db8a
--- /dev/null
+++ b/tests/cts/net/src/android/net/wifi/p2p/cts/WifiP2pConfigTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.p2p.cts;
+
+import android.net.MacAddress;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.test.AndroidTestCase;
+
+public class WifiP2pConfigTest extends AndroidTestCase {
+ static final String TEST_NETWORK_NAME = "DIRECT-xy-Hello";
+ static final String TEST_PASSPHRASE = "8etterW0r1d";
+ static final int TEST_OWNER_BAND = WifiP2pConfig.GROUP_OWNER_BAND_5GHZ;
+ static final int TEST_OWNER_FREQ = 2447;
+ static final String TEST_DEVICE_ADDRESS = "aa:bb:cc:dd:ee:ff";
+
+ public void testWifiP2pConfigBuilderForPersist() {
+ WifiP2pConfig.Builder builder = new WifiP2pConfig.Builder();
+ builder.setNetworkName(TEST_NETWORK_NAME)
+ .setPassphrase(TEST_PASSPHRASE)
+ .setGroupOperatingBand(TEST_OWNER_BAND)
+ .setDeviceAddress(MacAddress.fromString(TEST_DEVICE_ADDRESS))
+ .enablePersistentMode(true);
+ WifiP2pConfig config = builder.build();
+
+ assertTrue(config.deviceAddress.equals(TEST_DEVICE_ADDRESS));
+ assertTrue(config.networkName.equals(TEST_NETWORK_NAME));
+ assertTrue(config.passphrase.equals(TEST_PASSPHRASE));
+ assertEquals(config.groupOwnerBand, TEST_OWNER_BAND);
+ assertEquals(config.netId, WifiP2pGroup.PERSISTENT_NET_ID);
+ }
+
+ public void testWifiP2pConfigBuilderForNonPersist() {
+ WifiP2pConfig.Builder builder = new WifiP2pConfig.Builder();
+ builder.setNetworkName(TEST_NETWORK_NAME)
+ .setPassphrase(TEST_PASSPHRASE)
+ .setGroupOperatingFrequency(TEST_OWNER_FREQ)
+ .setDeviceAddress(MacAddress.fromString(TEST_DEVICE_ADDRESS))
+ .enablePersistentMode(false);
+ WifiP2pConfig config = builder.build();
+
+ assertTrue(config.deviceAddress.equals(TEST_DEVICE_ADDRESS));
+ assertTrue(config.networkName.equals(TEST_NETWORK_NAME));
+ assertTrue(config.passphrase.equals(TEST_PASSPHRASE));
+ assertEquals(config.groupOwnerBand, TEST_OWNER_FREQ);
+ assertEquals(config.netId, WifiP2pGroup.TEMPORARY_NET_ID);
+ }
+}
diff --git a/tests/cts/net/src/android/net/wifi/rtt/OWNERS b/tests/cts/net/src/android/net/wifi/rtt/OWNERS
deleted file mode 100644
index 4afc47f..0000000
--- a/tests/cts/net/src/android/net/wifi/rtt/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-etancohen@google.com
-satk@google.com
\ No newline at end of file
diff --git a/tests/cts/net/src/android/net/wifi/rtt/cts/TestBase.java b/tests/cts/net/src/android/net/wifi/rtt/cts/TestBase.java
index 57ea2a5..07d5718 100644
--- a/tests/cts/net/src/android/net/wifi/rtt/cts/TestBase.java
+++ b/tests/cts/net/src/android/net/wifi/rtt/cts/TestBase.java
@@ -32,6 +32,8 @@
import android.os.HandlerThread;
import android.test.AndroidTestCase;
+import com.android.compatibility.common.util.SystemUtil;
+
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
@@ -93,7 +95,7 @@
mWifiLock = mWifiManager.createWifiLock(TAG);
mWifiLock.acquire();
if (!mWifiManager.isWifiEnabled()) {
- mWifiManager.setWifiEnabled(true);
+ SystemUtil.runShellCommand("svc wifi enable");
}
IntentFilter intentFilter = new IntentFilter();
diff --git a/tests/cts/net/src/android/net/wifi/rtt/cts/WifiRttTest.java b/tests/cts/net/src/android/net/wifi/rtt/cts/WifiRttTest.java
index 74a0c3d..f71cadb 100644
--- a/tests/cts/net/src/android/net/wifi/rtt/cts/WifiRttTest.java
+++ b/tests/cts/net/src/android/net/wifi/rtt/cts/WifiRttTest.java
@@ -16,11 +16,10 @@
package android.net.wifi.rtt.cts;
-import android.content.IntentFilter;
import android.net.wifi.ScanResult;
import android.net.wifi.rtt.RangingRequest;
import android.net.wifi.rtt.RangingResult;
-import android.net.wifi.rtt.WifiRttManager;
+import android.platform.test.annotations.AppModeFull;
import com.android.compatibility.common.util.DeviceReportLog;
import com.android.compatibility.common.util.ResultType;
@@ -33,6 +32,7 @@
/**
* Wi-Fi RTT CTS test: range to all available Access Points which support IEEE 802.11mc.
*/
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public class WifiRttTest extends TestBase {
// Number of scans to do while searching for APs supporting IEEE 802.11mc
private static final int NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP = 2;
@@ -64,7 +64,10 @@
// Scan for IEEE 802.11mc supporting APs
ScanResult testAp = scanForTestAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
- assertTrue("Cannot find test AP", testAp != null);
+ assertTrue(
+ "Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
+ + "your test setup includes them!",
+ testAp != null);
// Perform RTT operations
RangingRequest request = new RangingRequest.Builder().addAccessPoint(testAp).build();
diff --git a/tests/cts/net/util/Android.bp b/tests/cts/net/util/Android.bp
new file mode 100644
index 0000000..1f94613
--- /dev/null
+++ b/tests/cts/net/util/Android.bp
@@ -0,0 +1,25 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// Common utilities for cts net tests.
+java_library {
+ name: "cts-net-utils",
+ srcs: ["java/**/*.java", "java/**/*.kt"],
+ static_libs: [
+ "compatibility-device-util-axt",
+ "junit",
+ ],
+}
\ No newline at end of file
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
new file mode 100644
index 0000000..e19d2ba
--- /dev/null
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts.util;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.State;
+import android.net.NetworkRequest;
+import android.net.wifi.WifiManager;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public final class CtsNetUtils {
+ private static final String TAG = CtsNetUtils.class.getSimpleName();
+ private static final int DURATION = 10000;
+ private static final int SOCKET_TIMEOUT_MS = 2000;
+
+ public static final int HTTP_PORT = 80;
+ public static final String TEST_HOST = "connectivitycheck.gstatic.com";
+ public static final String HTTP_REQUEST =
+ "GET /generate_204 HTTP/1.0\r\n" +
+ "Host: " + TEST_HOST + "\r\n" +
+ "Connection: keep-alive\r\n\r\n";
+ // Action sent to ConnectivityActionReceiver when a network callback is sent via PendingIntent.
+ public static final String NETWORK_CALLBACK_ACTION =
+ "ConnectivityManagerTest.NetworkCallbackAction";
+
+ private Context mContext;
+ private ConnectivityManager mCm;
+ private WifiManager mWifiManager;
+ private TestNetworkCallback mCellNetworkCallback;
+
+ public CtsNetUtils(Context context) {
+ mContext = context;
+ mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ }
+
+ // Toggle WiFi twice, leaving it in the state it started in
+ public void toggleWifi() {
+ if (mWifiManager.isWifiEnabled()) {
+ Network wifiNetwork = getWifiNetwork();
+ disconnectFromWifi(wifiNetwork);
+ connectToWifi();
+ } else {
+ connectToWifi();
+ Network wifiNetwork = getWifiNetwork();
+ disconnectFromWifi(wifiNetwork);
+ }
+ }
+
+ /** Enable WiFi and wait for it to become connected to a network. */
+ public Network connectToWifi() {
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
+ Network wifiNetwork = null;
+
+ ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
+ mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ mContext.registerReceiver(receiver, filter);
+
+ boolean connected = false;
+ try {
+ SystemUtil.runShellCommand("svc wifi enable");
+ // Ensure we get both an onAvailable callback and a CONNECTIVITY_ACTION.
+ wifiNetwork = callback.waitForAvailable();
+ assertNotNull(wifiNetwork);
+ connected = receiver.waitForState();
+ } catch (InterruptedException ex) {
+ fail("connectToWifi was interrupted");
+ } finally {
+ mCm.unregisterNetworkCallback(callback);
+ mContext.unregisterReceiver(receiver);
+ }
+
+ assertTrue("Wifi must be configured to connect to an access point for this test.",
+ connected);
+ return wifiNetwork;
+ }
+
+ /** Disable WiFi and wait for it to become disconnected from the network. */
+ public void disconnectFromWifi(Network wifiNetworkToCheck) {
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
+ Network lostWifiNetwork = null;
+
+ ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
+ mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.DISCONNECTED);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ mContext.registerReceiver(receiver, filter);
+
+ // Assert that we can establish a TCP connection on wifi.
+ Socket wifiBoundSocket = null;
+ if (wifiNetworkToCheck != null) {
+ try {
+ wifiBoundSocket = getBoundSocket(wifiNetworkToCheck, TEST_HOST, HTTP_PORT);
+ testHttpRequest(wifiBoundSocket);
+ } catch (IOException e) {
+ fail("HTTP request before wifi disconnected failed with: " + e);
+ }
+ }
+
+ boolean disconnected = false;
+ try {
+ SystemUtil.runShellCommand("svc wifi disable");
+ // Ensure we get both an onLost callback and a CONNECTIVITY_ACTION.
+ lostWifiNetwork = callback.waitForLost();
+ assertNotNull(lostWifiNetwork);
+ disconnected = receiver.waitForState();
+ } catch (InterruptedException ex) {
+ fail("disconnectFromWifi was interrupted");
+ } finally {
+ mCm.unregisterNetworkCallback(callback);
+ mContext.unregisterReceiver(receiver);
+ }
+
+ assertTrue("Wifi failed to reach DISCONNECTED state.", disconnected);
+
+ // Check that the socket is closed when wifi disconnects.
+ if (wifiBoundSocket != null) {
+ try {
+ testHttpRequest(wifiBoundSocket);
+ fail("HTTP request should not succeed after wifi disconnects");
+ } catch (IOException expected) {
+ assertEquals(Os.strerror(OsConstants.ECONNABORTED), expected.getMessage());
+ }
+ }
+ }
+
+ public Network getWifiNetwork() {
+ TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
+ Network network = null;
+ try {
+ network = callback.waitForAvailable();
+ } catch (InterruptedException e) {
+ fail("NetworkCallback wait was interrupted.");
+ } finally {
+ mCm.unregisterNetworkCallback(callback);
+ }
+ assertNotNull("Cannot find Network for wifi. Is wifi connected?", network);
+ return network;
+ }
+
+ public Network connectToCell() throws InterruptedException {
+ if (cellConnectAttempted()) {
+ throw new IllegalStateException("Already connected");
+ }
+ NetworkRequest cellRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build();
+ mCellNetworkCallback = new TestNetworkCallback();
+ mCm.requestNetwork(cellRequest, mCellNetworkCallback);
+ final Network cellNetwork = mCellNetworkCallback.waitForAvailable();
+ assertNotNull("Cell network not available. " +
+ "Please ensure the device has working mobile data.", cellNetwork);
+ return cellNetwork;
+ }
+
+ public void disconnectFromCell() {
+ if (!cellConnectAttempted()) {
+ throw new IllegalStateException("Cell connection not attempted");
+ }
+ mCm.unregisterNetworkCallback(mCellNetworkCallback);
+ mCellNetworkCallback = null;
+ }
+
+ public boolean cellConnectAttempted() {
+ return mCellNetworkCallback != null;
+ }
+
+ private NetworkRequest makeWifiNetworkRequest() {
+ return new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ }
+
+ private void testHttpRequest(Socket s) throws IOException {
+ OutputStream out = s.getOutputStream();
+ InputStream in = s.getInputStream();
+
+ final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8");
+ byte[] responseBytes = new byte[4096];
+ out.write(requestBytes);
+ in.read(responseBytes);
+ assertTrue(new String(responseBytes, "UTF-8").startsWith("HTTP/1.0 204 No Content\r\n"));
+ }
+
+ private Socket getBoundSocket(Network network, String host, int port) throws IOException {
+ InetSocketAddress addr = new InetSocketAddress(host, port);
+ Socket s = network.getSocketFactory().createSocket();
+ try {
+ s.setSoTimeout(SOCKET_TIMEOUT_MS);
+ s.connect(addr, SOCKET_TIMEOUT_MS);
+ } catch (IOException e) {
+ s.close();
+ throw e;
+ }
+ return s;
+ }
+
+ /**
+ * Receiver that captures the last connectivity change's network type and state. Recognizes
+ * both {@code CONNECTIVITY_ACTION} and {@code NETWORK_CALLBACK_ACTION} intents.
+ */
+ public static class ConnectivityActionReceiver extends BroadcastReceiver {
+
+ private final CountDownLatch mReceiveLatch = new CountDownLatch(1);
+
+ private final int mNetworkType;
+ private final NetworkInfo.State mNetState;
+ private final ConnectivityManager mCm;
+
+ public ConnectivityActionReceiver(ConnectivityManager cm, int networkType,
+ NetworkInfo.State netState) {
+ this.mCm = cm;
+ mNetworkType = networkType;
+ mNetState = netState;
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ NetworkInfo networkInfo = null;
+
+ // When receiving ConnectivityManager.CONNECTIVITY_ACTION, the NetworkInfo parcelable
+ // is stored in EXTRA_NETWORK_INFO. With a NETWORK_CALLBACK_ACTION, the Network is
+ // sent in EXTRA_NETWORK and we need to ask the ConnectivityManager for the NetworkInfo.
+ if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
+ networkInfo = intent.getExtras()
+ .getParcelable(ConnectivityManager.EXTRA_NETWORK_INFO);
+ assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK_INFO",
+ networkInfo);
+ } else if (NETWORK_CALLBACK_ACTION.equals(action)) {
+ Network network = intent.getExtras()
+ .getParcelable(ConnectivityManager.EXTRA_NETWORK);
+ assertNotNull("ConnectivityActionReceiver expected EXTRA_NETWORK", network);
+ networkInfo = this.mCm.getNetworkInfo(network);
+ if (networkInfo == null) {
+ // When disconnecting, it seems like we get an intent sent with an invalid
+ // Network; that is, by the time we call ConnectivityManager.getNetworkInfo(),
+ // it is invalid. Ignore these.
+ Log.i(TAG, "ConnectivityActionReceiver NETWORK_CALLBACK_ACTION ignoring "
+ + "invalid network");
+ return;
+ }
+ } else {
+ fail("ConnectivityActionReceiver received unxpected intent action: " + action);
+ }
+
+ assertNotNull("ConnectivityActionReceiver didn't find NetworkInfo", networkInfo);
+ int networkType = networkInfo.getType();
+ State networkState = networkInfo.getState();
+ Log.i(TAG, "Network type: " + networkType + " state: " + networkState);
+ if (networkType == mNetworkType && networkInfo.getState() == mNetState) {
+ mReceiveLatch.countDown();
+ }
+ }
+
+ public boolean waitForState() throws InterruptedException {
+ return mReceiveLatch.await(30, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * Callback used in testRegisterNetworkCallback that allows caller to block on
+ * {@code onAvailable}.
+ */
+ public static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+ private final CountDownLatch mAvailableLatch = new CountDownLatch(1);
+ private final CountDownLatch mLostLatch = new CountDownLatch(1);
+ private final CountDownLatch mUnavailableLatch = new CountDownLatch(1);
+
+ public Network currentNetwork;
+ public Network lastLostNetwork;
+
+ public Network waitForAvailable() throws InterruptedException {
+ return mAvailableLatch.await(30, TimeUnit.SECONDS) ? currentNetwork : null;
+ }
+
+ public Network waitForLost() throws InterruptedException {
+ return mLostLatch.await(30, TimeUnit.SECONDS) ? lastLostNetwork : null;
+ }
+
+ public boolean waitForUnavailable() throws InterruptedException {
+ return mUnavailableLatch.await(2, TimeUnit.SECONDS);
+ }
+
+
+ @Override
+ public void onAvailable(Network network) {
+ currentNetwork = network;
+ mAvailableLatch.countDown();
+ }
+
+ @Override
+ public void onLost(Network network) {
+ lastLostNetwork = network;
+ if (network.equals(currentNetwork)) {
+ currentNetwork = null;
+ }
+ mLostLatch.countDown();
+ }
+
+ @Override
+ public void onUnavailable() {
+ mUnavailableLatch.countDown();
+ }
+ }
+}