Merge "Fix testNetworkSettingsPermission for headless User 0" into qt-dev
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 0497470..0000000
--- a/tests/cts/net/Android.mk
+++ /dev/null
@@ -1,62 +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 \
- 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 := \
- FrameworksNetCommonTests \
- core-tests-support \
- compatibility-device-util-axt \
- ctstestrunner-axt \
- ctstestserver \
- mockwebserver \
- junit \
- junit-params \
- 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/api23Test/Android.bp b/tests/cts/net/api23Test/Android.bp
index 48161cf..ffe854e 100644
--- a/tests/cts/net/api23Test/Android.bp
+++ b/tests/cts/net/api23Test/Android.bp
@@ -31,6 +31,7 @@
static_libs: [
"core-tests-support",
"compatibility-device-util-axt",
+ "cts-net-utils",
"ctstestrunner-axt",
"ctstestserver",
"mockwebserver",
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
index f38490e..cdb66e3 100644
--- a/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java
+++ b/tests/cts/net/api23Test/src/android/net/cts/api23test/ConnectivityManagerApi23Test.java
@@ -25,58 +25,33 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
-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.net.cts.util.CtsNetUtils;
import android.os.Looper;
-import android.system.Os;
-import android.system.OsConstants;
import android.test.AndroidTestCase;
import android.util.Log;
-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.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class ConnectivityManagerApi23Test extends AndroidTestCase {
private static final String TAG = ConnectivityManagerApi23Test.class.getSimpleName();
-
- 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 HTTP_PORT = 80;
// 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 static final String NETWORK_CALLBACK_ACTION =
- "ConnectivityManagerTest.NetworkCallbackAction";
- 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";
private Context mContext;
- private ConnectivityManager mCm;
- private WifiManager mWifiManager;
private PackageManager mPackageManager;
+ private CtsNetUtils mCtsNetUtils;
@Override
protected void setUp() throws Exception {
super.setUp();
Looper.prepare();
mContext = getContext();
- mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mPackageManager = mContext.getPackageManager();
+ mCtsNetUtils = new CtsNetUtils(mContext);
}
/**
@@ -89,7 +64,7 @@
}
ConnectivityReceiver.prepare();
- toggleWifi();
+ 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.
@@ -112,7 +87,7 @@
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
Thread.sleep(200);
- toggleWifi();
+ mCtsNetUtils.toggleWifi();
Intent getConnectivityCount = new Intent(GET_WIFI_CONNECTIVITY_ACTION_COUNT);
assertEquals(2, sendOrderedBroadcastAndReturnResultCode(
@@ -130,7 +105,7 @@
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiver(receiver, filter);
- toggleWifi();
+ mCtsNetUtils.toggleWifi();
Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
finalIntent.setClass(mContext, ConnectivityReceiver.class);
mContext.sendBroadcast(finalIntent);
@@ -138,19 +113,6 @@
assertTrue(ConnectivityReceiver.waitForBroadcast());
}
- // 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);
- }
- }
-
private int sendOrderedBroadcastAndReturnResultCode(
Intent intent, int timeoutMs) throws InterruptedException {
final LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<>(1);
@@ -167,233 +129,4 @@
return resultCode;
}
- 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;
- }
-
- /** 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);
- }
- }
-
- 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());
- }
- }
- }
-
- /** 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 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.
- */
- 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();
- }
- }
}
\ No newline at end of file
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 ccb1278..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.cpp
-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/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/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/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index c444445..8c9bf6e 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -21,8 +21,12 @@
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;
@@ -34,7 +38,6 @@
import android.app.Instrumentation;
import android.app.PendingIntent;
import android.app.UiAutomation;
-import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -53,23 +56,23 @@
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.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.compatibility.common.util.SystemUtil;
import com.android.internal.R;
import com.android.internal.telephony.PhoneConstants;
@@ -107,8 +110,6 @@
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 CONNECT_TIMEOUT_MS = 2000;
private static final int KEEPALIVE_CALLBACK_TIMEOUT_MS = 2000;
private static final int KEEPALIVE_SOCKET_TIMEOUT_MS = 5000;
@@ -116,16 +117,6 @@
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";
-
- // Action sent to ConnectivityActionReceiver when a network callback is sent via PendingIntent.
- private static final String NETWORK_CALLBACK_ACTION =
- "ConnectivityManagerTest.NetworkCallbackAction";
-
// device could have only one interface: data, wifi.
private static final int MIN_NUM_NETWORK_TYPES = 1;
@@ -137,8 +128,8 @@
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
@@ -150,6 +141,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
@@ -174,10 +166,10 @@
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();
@@ -190,10 +182,10 @@
*/
private Network ensureWifiConnected() {
if (mWifiManager.isWifiEnabled()) {
- return getWifiNetwork();
+ return mCtsNetUtils.getWifiNetwork();
}
mWifiConnectAttempted = true;
- return connectToWifi();
+ return mCtsNetUtils.connectToWifi();
}
public void testIsNetworkTypeValid() {
@@ -301,8 +293,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);
@@ -320,33 +312,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.
@@ -508,7 +473,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.
@@ -567,7 +532,7 @@
public void testRequestNetworkCallback_onUnavailable() {
final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
if (previousWifiEnabledState) {
- disconnectFromWifi(null);
+ mCtsNetUtils.disconnectFromWifi(null);
}
final TestNetworkCallback callback = new TestNetworkCallback();
@@ -584,42 +549,11 @@
} finally {
mCm.unregisterNetworkCallback(callback);
if (previousWifiEnabledState) {
- connectToWifi();
+ mCtsNetUtils.connectToWifi();
}
}
}
- /** 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 {
- 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;
- }
-
private InetAddress getFirstV4Address(Network network) {
LinkProperties linkProperties = mCm.getLinkProperties(network);
for (InetAddress address : linkProperties.getAddresses()) {
@@ -630,199 +564,6 @@
return null;
}
- 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);
- }
- }
-
- 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());
- }
- }
- }
-
- /**
- * 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;
- }
-
/** Verify restricted networks cannot be requested. */
@AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
public void testRestrictedNetworks() {
@@ -1108,30 +849,6 @@
keepalivesPerTransport, nc);
}
- private boolean isKeepaliveSupported() throws Exception {
- final Network network = ensureWifiConnected();
- final Executor executor = mContext.getMainExecutor();
- final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
- try (Socket s = getConnectedSocket(network, TEST_HOST,
- HTTP_PORT, KEEPALIVE_SOCKET_TIMEOUT_MS, AF_INET);
- SocketKeepalive sk = mCm.createSocketKeepalive(network, s, executor, callback)) {
- sk.start(MIN_KEEPALIVE_INTERVAL);
- final TestSocketKeepaliveCallback.CallbackValue result = callback.pollCallback();
- switch (result.callbackType) {
- case ON_STARTED:
- sk.stop();
- callback.expectStopped();
- return true;
- case ON_ERROR:
- if (result.error == SocketKeepalive.ERROR_UNSUPPORTED) return false;
- // else fallthrough.
- default:
- fail("Got unexpected callback: " + result);
- return false;
- }
- }
- }
-
private void adoptShellPermissionIdentity() {
mUiAutomation.adoptShellPermissionIdentity();
mShellPermissionIdentityAdopted = true;
@@ -1144,14 +861,88 @@
}
}
+ 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 testKeepaliveUnsupported() throws Exception {
+ if (getSupportedKeepalivesFromRes() != 0) return;
+
+ adoptShellPermissionIdentity();
+
+ assertEquals(0, createConcurrentSocketKeepalives(1, 0));
+ assertEquals(0, createConcurrentSocketKeepalives(0, 1));
+
+ dropShellPermissionIdentity();
+ }
+
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public void testCreateTcpKeepalive() throws Exception {
adoptShellPermissionIdentity();
- if (!isKeepaliveSupported()) return;
+ if (getSupportedKeepalivesFromRes() == 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(0, 1));
+ Log.i(TAG, "testCreateTcpKeepalive is skipped for kernel "
+ + VintfRuntimeInfo.getKernelVersion());
+ return;
+ }
final Network network = ensureWifiConnected();
- final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8");
+ 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,
@@ -1213,14 +1004,16 @@
sk.start(MIN_KEEPALIVE_INTERVAL);
callback.expectError(SocketKeepalive.ERROR_SOCKET_NOT_IDLE);
}
-
}
}
+ /**
+ * 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(int nattCount, int tcpCount) throws Exception {
- // Use customization value in resource to prevent the need of privilege.
- if (getSupportedKeepalivesFromRes() == 0) return 0;
-
final Network network = ensureWifiConnected();
final ArrayList<SocketKeepalive> kalist = new ArrayList<>();
@@ -1239,10 +1032,14 @@
ka.start(MIN_KEEPALIVE_INTERVAL);
TestSocketKeepaliveCallback.CallbackValue cv = callback.pollCallback();
assertNotNull(cv);
- if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_ERROR
- && cv.error == SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES) {
- // Limit reached.
- break;
+ if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_ERROR) {
+ if (i == 0 && cv.error == SocketKeepalive.ERROR_UNSUPPORTED) {
+ // Unsupported.
+ break;
+ } else if (i != 0 && cv.error == SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES) {
+ // Limit reached.
+ break;
+ }
}
if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_STARTED) {
kalist.add(ka);
@@ -1268,10 +1065,14 @@
ka.start(MIN_KEEPALIVE_INTERVAL);
TestSocketKeepaliveCallback.CallbackValue cv = callback.pollCallback();
assertNotNull(cv);
- if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_ERROR
- && cv.error == SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES) {
- // Limit reached.
- break;
+ if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_ERROR) {
+ if (i == 0 && cv.error == SocketKeepalive.ERROR_UNSUPPORTED) {
+ // Unsupported.
+ break;
+ } else if (i != 0 && cv.error == SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES) {
+ // Limit reached.
+ break;
+ }
}
if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_STARTED) {
kalist.add(ka);
@@ -1299,32 +1100,35 @@
*/
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public void testSocketKeepaliveLimit() throws Exception {
- adoptShellPermissionIdentity();
-
final int supported = getSupportedKeepalivesFromRes();
-
- if (!isKeepaliveSupported()) {
- // Sanity check.
- assertEquals(0, supported);
+ if (supported == 0) {
return;
}
+ adoptShellPermissionIdentity();
+
// Verifies that the supported keepalive slots meet MIN_SUPPORTED_KEEPALIVE_COUNT.
assertGreaterOrEqual(supported, KeepaliveUtils.MIN_SUPPORTED_KEEPALIVE_COUNT);
- // Verifies that different types of keepalives can be established.
+ // Verifies that Nat-T keepalives can be established.
assertEquals(supported, createConcurrentSocketKeepalives(supported + 1, 0));
- assertEquals(supported, createConcurrentSocketKeepalives(0, supported + 1));
-
- // Verifies that different types can be established at the same time.
- assertEquals(supported, createConcurrentSocketKeepalives(
- supported / 2, supported - supported / 2));
-
// Verifies that keepalives don't get leaked in second round.
assertEquals(supported, createConcurrentSocketKeepalives(supported + 1, 0));
- assertEquals(supported, createConcurrentSocketKeepalives(0, supported + 1));
- assertEquals(supported, createConcurrentSocketKeepalives(
- supported / 2, supported - supported / 2));
+
+ // 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(0, supported + 1));
+
+ // Verifies that different types can be established at the same time.
+ assertEquals(supported, createConcurrentSocketKeepalives(
+ supported / 2, supported - supported / 2));
+
+ // Verifies that keepalives don't get leaked in second round.
+ assertEquals(supported, createConcurrentSocketKeepalives(0, supported + 1));
+ assertEquals(supported, createConcurrentSocketKeepalives(
+ supported / 2, supported - supported / 2));
+ }
dropShellPermissionIdentity();
}
@@ -1335,14 +1139,9 @@
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public void testSocketKeepaliveUnprivileged() throws Exception {
final int supported = getSupportedKeepalivesFromRes();
-
- adoptShellPermissionIdentity();
- if (!isKeepaliveSupported()) {
- // Sanity check.
- assertEquals(0, supported);
+ if (supported == 0) {
return;
}
- dropShellPermissionIdentity();
final int allowedUnprivilegedPerUid = mContext.getResources().getInteger(
R.integer.config_allowedUnprivilegedKeepalivePerUid);
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index 40d64cf..e16fce0 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -21,20 +21,25 @@
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.EBADF;
+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.provider.Settings;
import android.system.ErrnoException;
import android.test.AndroidTestCase;
import android.util.Log;
@@ -53,20 +58,63 @@
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 NXDOMAIN = 3;
+ static final int PRIVATE_DNS_SETTING_TIMEOUT_MS = 2_000;
+ private ContentResolver mCR;
private ConnectivityManager mCM;
private Executor mExecutor;
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;
+ 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) {
@@ -94,6 +142,9 @@
"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]);
}
@@ -105,15 +156,12 @@
public DnsParseException(String msg) {
super(msg);
}
-
- public DnsParseException(String msg, Throwable cause) {
- super(msg, cause);
- }
}
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");
@@ -123,10 +171,12 @@
int getRcode() {
return mHeader.rcode;
}
- int getANCount(){
+
+ int getANCount() {
return mHeader.getRecordCount(ANSECTION);
}
- int getQDCount(){
+
+ int getQDCount() {
return mHeader.getRecordCount(QDSECTION);
}
}
@@ -144,9 +194,8 @@
private DnsAnswer mDnsAnswer;
VerifyCancelCallback(@NonNull String msg, @Nullable CancellationSignal cancel) {
- this.mMsg = msg;
- this.mCancelSignal = cancel;
- this.mDnsAnswer = null;
+ mMsg = msg;
+ mCancelSignal = cancel;
}
VerifyCancelCallback(@NonNull String msg) {
@@ -174,7 +223,7 @@
mRcode = rcode;
try {
mDnsAnswer = new DnsAnswer(answer);
- } catch (DnsParseException e) {
+ } catch (ParseException | DnsParseException e) {
fail(mMsg + e.getMessage());
}
Log.d(TAG, "Reported blob: " + byteArrayToHexString(answer));
@@ -187,15 +236,15 @@
}
private void assertValidAnswer() {
- assertTrue(mMsg + "No valid answer", mDnsAnswer != null);
- assertTrue(mMsg + " Unexpected error: reported rcode" + mRcode +
- " blob's rcode " + mDnsAnswer.getRcode(), mRcode == mDnsAnswer.getRcode());
+ 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).
- assertTrue(mMsg + " Response error, rcode: " + mRcode, mRcode == 0);
+ assertEquals(mMsg + " Response error, rcode: " + mRcode, mRcode, 0);
// Check answer counts.
assertGreaterThan(mMsg + " No answer found", mDnsAnswer.getANCount(), 0);
// Check question counts.
@@ -205,9 +254,9 @@
public void assertNXDomain() {
assertValidAnswer();
// Check rcode field.(3, NXDomain).
- assertTrue(mMsg + " Unexpected rcode: " + mRcode, mRcode == NXDOMAIN);
+ assertEquals(mMsg + " Unexpected rcode: " + mRcode, mRcode, NXDOMAIN);
// Check answer counts. Expect 0 answer.
- assertTrue(mMsg + " Not an empty answer", mDnsAnswer.getANCount() == 0);
+ assertEquals(mMsg + " Not an empty answer", mDnsAnswer.getANCount(), 0);
// Check question counts.
assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
}
@@ -215,98 +264,84 @@
public void assertEmptyAnswer() {
assertValidAnswer();
// Check rcode field.(0, No error condition).
- assertTrue(mMsg + " Response error, rcode: " + mRcode, mRcode == 0);
+ assertEquals(mMsg + " Response error, rcode: " + mRcode, mRcode, 0);
// Check answer counts. Expect 0 answer.
- assertTrue(mMsg + " Not an empty answer", mDnsAnswer.getANCount() == 0);
+ assertEquals(mMsg + " Not an empty answer", mDnsAnswer.getANCount(), 0);
// Check question counts.
assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
}
}
- public void testRawQuery() {
- final String dname = "www.google.com";
- final String msg = "RawQuery " + dname;
+ public void testRawQuery() throws InterruptedException {
+ final String msg = "RawQuery " + TEST_DOMAIN;
for (Network network : getTestableNetworks()) {
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
- mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
+ mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
mExecutor, null, callback);
- try {
- assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
- callback.waitForAnswer());
- callback.assertHasAnswer();
- } catch (InterruptedException e) {
- fail(msg + " Waiting for DNS lookup was interrupted");
- }
+
+ assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+ callback.waitForAnswer());
+ callback.assertHasAnswer();
}
}
- public void testRawQueryBlob() {
+ public void testRawQueryBlob() 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 */
+ /* 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, mExecutor, null, callback);
- try {
- assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
- callback.waitForAnswer());
- callback.assertHasAnswer();
- } catch (InterruptedException e) {
- fail(msg + " Waiting for DNS lookup was interrupted");
- }
+
+ assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+ callback.waitForAnswer());
+ callback.assertHasAnswer();
}
}
- public void testRawQueryRoot() {
+ public void testRawQueryRoot() 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,
mExecutor, null, callback);
- try {
- assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
- callback.waitForAnswer());
- // Except no answer record because of querying with empty dname(ROOT)
- callback.assertEmptyAnswer();
- } catch (InterruptedException e) {
- fail(msg + "Waiting for DNS lookup was interrupted");
- }
+
+ 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 testRawQueryNXDomain() {
+ public void testRawQueryNXDomain() 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,
mExecutor, null, callback);
- try {
- assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
- callback.waitForAnswer());
- callback.assertNXDomain();
- } catch (InterruptedException e) {
- fail(msg + " Waiting for DNS lookup was interrupted");
- }
+
+ assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+ callback.waitForAnswer());
+ callback.assertNXDomain();
}
}
- public void testRawQueryCancel() throws ErrnoException {
- final String dname = "www.google.com";
- final String msg = "Test cancel RawQuery " + dname;
+ 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.
@@ -320,39 +355,22 @@
final CountDownLatch latch = new CountDownLatch(1);
final CancellationSignal cancelSignal = new CancellationSignal();
final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
- mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
+ mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
mExecutor, cancelSignal, callback);
mExecutor.execute(() -> {
cancelSignal.cancel();
latch.countDown();
});
- try {
- retry = callback.needRetry();
- assertTrue(msg + " query was not cancelled",
- latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(msg + "Waiting for DNS lookup was interrupted");
- }
+
+ retry = callback.needRetry();
+ assertTrue(msg + " query was not cancelled",
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} while (retry);
}
}
- public void testRawQueryBlobCancel() throws ErrnoException {
- 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 = "Test cancel RawQuery blob " + byteArrayToHexString(blob);
+ 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.
@@ -366,37 +384,30 @@
final CountDownLatch latch = new CountDownLatch(1);
final CancellationSignal cancelSignal = new CancellationSignal();
final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
- mDns.rawQuery(network, blob, FLAG_EMPTY, mExecutor, cancelSignal, callback);
+ mDns.rawQuery(network, TEST_BLOB, FLAG_EMPTY, mExecutor, cancelSignal, callback);
mExecutor.execute(() -> {
cancelSignal.cancel();
latch.countDown();
});
- try {
- retry = callback.needRetry();
- assertTrue(msg + " cancel is not done",
- latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(msg + " Waiting for DNS lookup was interrupted");
- }
+
+ retry = callback.needRetry();
+ assertTrue(msg + " cancel is not done",
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} while (retry);
}
}
- public void testCancelBeforeQuery() throws ErrnoException {
- final String dname = "www.google.com";
- final String msg = "Test cancelled RawQuery " + dname;
+ 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, dname, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
+ mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
mExecutor, cancelSignal, callback);
- try {
- assertTrue(msg + " should not return any answers",
- !callback.waitForAnswer(CANCEL_TIMEOUT_MS));
- } catch (InterruptedException e) {
- fail(msg + " Waiting for DNS lookup was interrupted");
- }
+
+ assertTrue(msg + " should not return any answers",
+ !callback.waitForAnswer(CANCEL_TIMEOUT_MS));
}
}
@@ -463,27 +474,21 @@
}
}
- public void testQueryForInetAddress() {
- final String dname = "www.google.com";
- final String msg = "Test query for InetAddress " + dname;
+ public void testQueryForInetAddress() 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, dname, FLAG_NO_CACHE_LOOKUP,
- mExecutor, null, callback);
- try {
- assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
- callback.waitForAnswer());
- assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
- } catch (InterruptedException e) {
- fail(msg + " Waiting for DNS lookup was interrupted");
- }
+ mDns.query(network, TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callback);
+
+ assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+ callback.waitForAnswer());
+ assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
}
}
- public void testQueryCancelForInetAddress() throws ErrnoException {
- final String dname = "www.google.com";
- final String msg = "Test cancel query for InetAddress " + dname;
+ 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.
@@ -498,57 +503,131 @@
final CancellationSignal cancelSignal = new CancellationSignal();
final VerifyCancelInetAddressCallback callback =
new VerifyCancelInetAddressCallback(msg, cancelSignal);
- mDns.query(network, dname, FLAG_EMPTY, mExecutor, cancelSignal, callback);
+ mDns.query(network, TEST_DOMAIN, FLAG_EMPTY, mExecutor, cancelSignal, callback);
mExecutor.execute(() -> {
cancelSignal.cancel();
latch.countDown();
});
- try {
- retry = callback.needRetry();
- assertTrue(msg + " query was not cancelled",
- latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail(msg + "Waiting for DNS lookup was interrupted");
- }
+
+ retry = callback.needRetry();
+ assertTrue(msg + " query was not cancelled",
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} while (retry);
}
}
- public void testQueryForInetAddressIpv4() {
- final String dname = "www.google.com";
- final String msg = "Test query for IPv4 InetAddress " + dname;
+ public void testQueryForInetAddressIpv4() 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, dname, TYPE_A, FLAG_NO_CACHE_LOOKUP,
+ mDns.query(network, TEST_DOMAIN, TYPE_A, FLAG_NO_CACHE_LOOKUP,
mExecutor, null, callback);
- try {
- 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());
- } catch (InterruptedException e) {
- fail(msg + " Waiting for DNS lookup was interrupted");
- }
+
+ 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 testQueryForInetAddressIpv6() {
- final String dname = "www.google.com";
- final String msg = "Test query for IPv6 InetAddress " + dname;
+ public void testQueryForInetAddressIpv6() 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, dname, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
+ mDns.query(network, TEST_DOMAIN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
mExecutor, null, callback);
- try {
- 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());
- } catch (InterruptedException e) {
- fail(msg + " Waiting for DNS lookup was interrupted");
+
+ 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);
}
}
}
diff --git a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
index 26049cc..10e43e7 100644
--- a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
@@ -76,11 +76,9 @@
protected ConnectivityManager mCM;
protected IpSecManager mISM;
- protected Context mContext;
@Before
public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getContext();
mISM =
(IpSecManager)
InstrumentationRegistry.getContext()
@@ -475,7 +473,7 @@
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);
}
}
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index 1241785..355b496 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -41,6 +41,7 @@
import android.system.Os;
import android.system.OsConstants;
+import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import java.io.FileDescriptor;
@@ -73,12 +74,6 @@
private static final byte[] AEAD_KEY = getKey(288);
- @Before
- @Override
- public void setUp() throws Exception {
- super.setUp();
- }
-
/*
* Allocate a random SPI
* Allocate a specific SPI using previous randomly created SPI value
@@ -244,7 +239,7 @@
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(
@@ -462,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);
}
@@ -623,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())
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
index 93638ac..999d2f1 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
@@ -59,6 +59,7 @@
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;
@@ -79,6 +80,7 @@
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();
@@ -127,15 +129,15 @@
// right appop permissions.
setAppop(OP_MANAGE_IPSEC_TUNNELS, true);
- TestNetworkInterface testIntf =
+ TestNetworkInterface testIface =
sTNM.createTunInterface(
new LinkAddress[] {
new LinkAddress(LOCAL_OUTER_4, IP4_PREFIX_LEN),
new LinkAddress(LOCAL_OUTER_6, IP6_PREFIX_LEN)
});
- sTunFd = testIntf.getFileDescriptor();
- sTunNetworkCallback = setupAndGetTestNetwork(testIntf.getInterfaceName());
+ sTunFd = testIface.getFileDescriptor();
+ sTunNetworkCallback = setupAndGetTestNetwork(testIface.getInterfaceName());
sTunNetwork = sTunNetworkCallback.getNetworkBlocking();
sTunUtils = new TunUtils(sTunFd);
@@ -240,8 +242,16 @@
}
/* Test runnables for callbacks after IPsec tunnels are set up. */
- private interface TestRunnable {
- void run(Network ipsecNetwork) throws Exception;
+ 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 {
@@ -288,8 +298,8 @@
return expectedPacketSize;
}
- private interface TestRunnableFactory {
- TestRunnable getTestRunnable(
+ private interface IpSecTunnelTestRunnableFactory {
+ IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
boolean transportInTunnelMode,
int spi,
InetAddress localInner,
@@ -299,12 +309,13 @@
IpSecTransform inTransportTransform,
IpSecTransform outTransportTransform,
int encapPort,
+ int innerSocketPort,
int expectedPacketSize)
throws Exception;
}
- private class OutputTestRunnableFactory implements TestRunnableFactory {
- public TestRunnable getTestRunnable(
+ private class OutputIpSecTunnelTestRunnableFactory implements IpSecTunnelTestRunnableFactory {
+ public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
boolean transportInTunnelMode,
int spi,
InetAddress localInner,
@@ -314,13 +325,15 @@
IpSecTransform inTransportTransform,
IpSecTransform outTransportTransform,
int encapPort,
+ int unusedInnerSocketPort,
int expectedPacketSize) {
- return new TestRunnable() {
+ return new IpSecTunnelTestRunnable() {
@Override
- public void run(Network ipsecNetwork) throws Exception {
+ 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) {
@@ -333,19 +346,22 @@
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 our not knowing some of the fields of the
+ // 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 InputPacketGeneratorTestRunnableFactory implements TestRunnableFactory {
- public TestRunnable getTestRunnable(
+ private class InputReflectedIpSecTunnelTestRunnableFactory
+ implements IpSecTunnelTestRunnableFactory {
+ public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
boolean transportInTunnelMode,
int spi,
InetAddress localInner,
@@ -355,14 +371,57 @@
IpSecTransform inTransportTransform,
IpSecTransform outTransportTransform,
int encapPort,
+ int innerSocketPort,
int expectedPacketSize)
throws Exception {
- return new TestRunnable() {
+ return new IpSecTunnelTestRunnable() {
@Override
- public void run(Network ipsecNetwork) throws Exception {
+ 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);
- // JavaUdpSocket socket = new JavaUdpSocket(localInner, socketPort.get());
ipsecNetwork.bindSocket(socket.mSocket);
// For Transport-In-Tunnel mode, apply transform to socket
@@ -402,6 +461,8 @@
receiveAndValidatePacket(socket);
socket.close();
+
+ return 0;
}
};
}
@@ -415,7 +476,7 @@
outerFamily,
useEncap,
transportInTunnelMode,
- new OutputTestRunnableFactory());
+ new OutputIpSecTunnelTestRunnableFactory());
}
private void checkTunnelInput(
@@ -426,7 +487,91 @@
outerFamily,
useEncap,
transportInTunnelMode,
- new InputPacketGeneratorTestRunnableFactory());
+ 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(
@@ -434,7 +579,7 @@
int outerFamily,
boolean useEncap,
boolean transportInTunnelMode,
- TestRunnableFactory factory)
+ IpSecTunnelTestRunnableFactory factory)
throws Exception {
if (!hasTunnelsFeature()) return;
@@ -461,14 +606,14 @@
buildIpSecTransform(sContext, outTransportSpi, null, localInner);
UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
- buildTunnelAndNetwork(
+ buildTunnelNetworkAndRunTests(
localInner,
remoteInner,
localOuter,
remoteOuter,
spi,
useEncap ? encapSocket : null,
- factory.getTestRunnable(
+ factory.getIpSecTunnelTestRunnable(
transportInTunnelMode,
spi,
localInner,
@@ -478,41 +623,42 @@
inTransportTransform,
outTransportTransform,
useEncap ? encapSocket.getPort() : 0,
+ 0,
expectedPacketSize));
}
}
- private void buildTunnelAndNetwork(
+ private int buildTunnelNetworkAndRunTests(
InetAddress localInner,
InetAddress remoteInner,
InetAddress localOuter,
InetAddress remoteOuter,
int spi,
UdpEncapsulationSocket encapSocket,
- TestRunnable test)
+ 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 tunnelIntf =
+ IpSecManager.IpSecTunnelInterface tunnelIface =
mISM.createIpSecTunnelInterface(localOuter, remoteOuter, sTunNetwork)) {
// Build the test network
- tunnelIntf.addAddress(localInner, innerPrefixLen);
- testNetworkCb = setupAndGetTestNetwork(tunnelIntf.getInterfaceName());
+ tunnelIface.addAddress(localInner, innerPrefixLen);
+ testNetworkCb = setupAndGetTestNetwork(tunnelIface.getInterfaceName());
Network testNetwork = testNetworkCb.getNetworkBlocking();
// Check interface was created
- NetworkInterface netIntf = NetworkInterface.getByName(tunnelIntf.getInterfaceName());
- assertNotNull(netIntf);
+ assertNotNull(NetworkInterface.getByName(tunnelIface.getInterfaceName()));
// Verify address was added
- netIntf = NetworkInterface.getByInetAddress(localInner);
- assertNotNull(netIntf);
- assertEquals(tunnelIntf.getInterfaceName(), netIntf.getDisplayName());
+ final NetworkInterface netIface = NetworkInterface.getByInetAddress(localInner);
+ assertNotNull(netIface);
+ assertEquals(tunnelIface.getInterfaceName(), netIface.getDisplayName());
// Configure Transform parameters
IpSecTransform.Builder transformBuilder = new IpSecTransform.Builder(sContext);
@@ -531,28 +677,31 @@
transformBuilder.buildTunnelModeTransform(remoteOuter, inSpi);
IpSecTransform outTransform =
transformBuilder.buildTunnelModeTransform(localOuter, outSpi)) {
- mISM.applyTunnelModeTransform(tunnelIntf, IpSecManager.DIRECTION_IN, inTransform);
- mISM.applyTunnelModeTransform(tunnelIntf, IpSecManager.DIRECTION_OUT, outTransform);
+ mISM.applyTunnelModeTransform(tunnelIface, IpSecManager.DIRECTION_IN, inTransform);
+ mISM.applyTunnelModeTransform(
+ tunnelIface, IpSecManager.DIRECTION_OUT, outTransform);
- test.run(testNetwork);
+ 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
- tunnelIntf.removeAddress(localInner, innerPrefixLen);
- assertNotNull(NetworkInterface.getByName(tunnelIntf.getInterfaceName()));
+ tunnelIface.removeAddress(localInner, innerPrefixLen);
+ assertNotNull(NetworkInterface.getByName(tunnelIface.getInterfaceName()));
assertNull(NetworkInterface.getByInetAddress(localInner));
// Check interface was cleaned up
- tunnelIntf.close();
- assertNull(NetworkInterface.getByName(tunnelIntf.getInterfaceName()));
+ tunnelIface.close();
+ assertNull(NetworkInterface.getByName(tunnelIface.getInterfaceName()));
} finally {
if (testNetworkCb != null) {
sCM.unregisterNetworkCallback(testNetworkCb);
}
}
+
+ return innerSocketPort;
}
private static void receiveAndValidatePacket(JavaUdpSocket socket) throws Exception {
@@ -676,35 +825,65 @@
}
@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 {
@@ -713,32 +892,62 @@
}
@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/NetworkWatchlistTest.java b/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java
index cb0cda8..e4e350c 100644
--- a/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java
@@ -25,6 +25,7 @@
import android.net.ConnectivityManager;
import android.platform.test.annotations.AppModeFull;
import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -39,7 +40,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Formatter;
@@ -51,8 +52,6 @@
private static final String TEST_WATCHLIST_XML = "assets/network_watchlist_config_for_test.xml";
private static final String TEST_EMPTY_WATCHLIST_XML =
"assets/network_watchlist_config_empty_for_test.xml";
- private static final String SDCARD_CONFIG_PATH =
- "/sdcard/network_watchlist_config_for_test.xml";
private static final String TMP_CONFIG_PATH =
"/data/local/tmp/network_watchlist_config_for_test.xml";
// Generated from sha256sum network_watchlist_config_for_test.xml
@@ -84,8 +83,7 @@
}
}
- private void cleanup() throws Exception {
- runCommand("rm " + SDCARD_CONFIG_PATH);
+ private void cleanup() throws IOException {
runCommand("rm " + TMP_CONFIG_PATH);
}
@@ -118,22 +116,43 @@
}
private void saveResourceToFile(String res, String filePath) throws IOException {
- InputStream in = getClass().getClassLoader().getResourceAsStream(res);
- FileUtils.copyToFileOrThrow(in, new File(filePath));
+ // App can't access /data/local/tmp directly, so we pipe resource to file through stdin.
+ ParcelFileDescriptor stdin = pipeFromStdin(filePath);
+ pipeResourceToFileDescriptor(res, stdin);
+ }
+
+ /* Pipe stdin to a file in filePath. Returns PFD for stdin. */
+ private ParcelFileDescriptor pipeFromStdin(String filePath) {
+ // Not all devices have symlink for /dev/stdin, so use /proc/self/fd/0 directly.
+ // /dev/stdin maps to /proc/self/fd/0.
+ return runRwCommand("cp /proc/self/fd/0 " + filePath)[1];
+ }
+
+ private void pipeResourceToFileDescriptor(String res, ParcelFileDescriptor pfd)
+ throws IOException {
+ InputStream resStream = getClass().getClassLoader().getResourceAsStream(res);
+ FileOutputStream fdStream = new ParcelFileDescriptor.AutoCloseOutputStream(pfd);
+
+ FileUtils.copy(resStream, fdStream);
+
+ try {
+ fdStream.close();
+ } catch (IOException e) {
+ }
}
private static String runCommand(String command) throws IOException {
return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
}
+ private static ParcelFileDescriptor[] runRwCommand(String command) {
+ return InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation().executeShellCommandRw(command);
+ }
+
private void setWatchlistConfig(String watchlistConfigFile) throws Exception {
cleanup();
- // Save test watchlist config to sdcard as app can't access /data/local/tmp
- saveResourceToFile(watchlistConfigFile, SDCARD_CONFIG_PATH);
- // Copy test watchlist config from sdcard to /data/local/tmp as system service
- // can't access /sdcard
- runCommand("cp " + SDCARD_CONFIG_PATH + " " + TMP_CONFIG_PATH);
- // Set test watchlist config to system
+ saveResourceToFile(watchlistConfigFile, TMP_CONFIG_PATH);
final String cmdResult = runCommand(
"cmd network_watchlist set-test-config " + TMP_CONFIG_PATH).trim();
assertThat(cmdResult).contains("Success");
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 fbf1abd..0ef5cd9 100644
--- a/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/cts/net/src/android/net/wifi/cts/WifiManagerTest.java
@@ -41,6 +41,7 @@
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;
@@ -798,6 +799,8 @@
* 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);
@@ -805,16 +808,39 @@
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 List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[] {
- android.Manifest.permission.NETWORK_SETUP_WIZARD
+ 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 (!Objects.equals(pi.packageName, validPkg)) {
+ 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 +"]");
+ + " and must be revoked for security reasons [" + validPkg + "]");
}
}
}
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();
+ }
+ }
+}