[automerger skipped] Merge "DO NOT MERGE:CDD Annotation for 7.4.7/C-2-1" into pie-cts-dev
am: 9709b60914 -s ours
am skip reason: subject contains skip directive
Change-Id: Ic4a66d08a162dc0685b0ad5dd08d63be9705a39d
diff --git a/tests/cts/hostside/aidl/Android.mk b/tests/cts/hostside/aidl/Android.mk
index 85f71c3..20dabc1 100644
--- a/tests/cts/hostside/aidl/Android.mk
+++ b/tests/cts/hostside/aidl/Android.mk
@@ -19,6 +19,7 @@
LOCAL_SDK_VERSION := current
LOCAL_SRC_FILES := \
com/android/cts/net/hostside/IMyService.aidl \
+ com/android/cts/net/hostside/INetworkCallback.aidl \
com/android/cts/net/hostside/INetworkStateObserver.aidl \
com/android/cts/net/hostside/IRemoteSocketFactory.aidl
LOCAL_MODULE := CtsHostsideNetworkTestsAidl
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
index 72d1059..a820ae5 100644
--- a/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
+++ b/tests/cts/hostside/aidl/com/android/cts/net/hostside/IMyService.aidl
@@ -16,10 +16,13 @@
package com.android.cts.net.hostside;
+import com.android.cts.net.hostside.INetworkCallback;
+
interface IMyService {
void registerBroadcastReceiver();
int getCounters(String receiverName, String action);
String checkNetworkStatus();
String getRestrictBackgroundStatus();
void sendNotification(int notificationId, String notificationType);
+ void registerNetworkCallback(in INetworkCallback cb);
}
diff --git a/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkCallback.aidl b/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkCallback.aidl
new file mode 100644
index 0000000..740ec26
--- /dev/null
+++ b/tests/cts/hostside/aidl/com/android/cts/net/hostside/INetworkCallback.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net.hostside;
+
+import android.net.Network;
+
+interface INetworkCallback {
+ void onBlockedStatusChanged(in Network network, boolean blocked);
+ void onAvailable(in Network network);
+ void onLost(in Network network);
+}
diff --git a/tests/cts/hostside/app/Android.mk b/tests/cts/hostside/app/Android.mk
index 6d89e58..11f6bb1 100644
--- a/tests/cts/hostside/app/Android.mk
+++ b/tests/cts/hostside/app/Android.mk
@@ -19,7 +19,8 @@
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
+#LOCAL_SDK_VERSION := current
+LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util-axt ctstestrunner-axt ub-uiautomator \
CtsHostsideNetworkTestsAidl
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 5232372..bbc0354 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -370,6 +370,23 @@
}
/**
+ * As per CDD requirements, if the device doesn't support data saver mode then
+ * ConnectivityManager.getRestrictBackgroundStatus() will always return
+ * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
+ * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
+ * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
+ */
+ protected boolean isDataSaverSupported() throws Exception {
+ assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
+ try {
+ setRestrictBackground(true);
+ return !isMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
+ } finally {
+ setRestrictBackground(false);
+ }
+ }
+
+ /**
* Returns whether an app state should be considered "background" for restriction purposes.
*/
protected boolean isBackground(int state) {
@@ -962,6 +979,10 @@
fail("app2 receiver is not ready");
}
+ protected void registerNetworkCallback(INetworkCallback cb) throws Exception {
+ mServiceClient.registerNetworkCallback(cb);
+ }
+
/**
* Registers a {@link NotificationListenerService} implementation that will execute the
* notification actions right after the notification is sent.
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
index c3962fb..72563d4 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
@@ -22,9 +22,6 @@
import android.util.Log;
-import com.android.compatibility.common.util.CddTest;
-
-@CddTest(requirement="7.4.7/C-1-1,H-1-1,C-2-1")
public class DataSaverModeTest extends AbstractRestrictBackgroundNetworkTestCase {
private static final String[] REQUIRED_WHITELISTED_PACKAGES = {
@@ -76,23 +73,6 @@
return mIsDataSaverSupported && super.isSupported();
}
- /**
- * As per CDD requirements, if the device doesn't support data saver mode then
- * ConnectivityManager.getRestrictBackgroundStatus() will always return
- * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
- * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
- * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
- */
- private boolean isDataSaverSupported() throws Exception {
- assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
- try {
- setRestrictBackground(true);
- return !isMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
- } finally {
- setRestrictBackground(false);
- }
- }
-
public void testGetRestrictBackgroundStatus_disabled() throws Exception {
if (!isSupported()) return;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
index e2976c2..3ee7b99 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyServiceClient.java
@@ -26,8 +26,6 @@
import com.android.cts.net.hostside.IMyService;
-import java.io.FileDescriptor;
-
public class MyServiceClient {
private static final int TIMEOUT_MS = 5000;
private static final String PACKAGE = MyServiceClient.class.getPackage().getName();
@@ -98,4 +96,8 @@
public void sendNotification(int notificationId, String notificationType) throws RemoteException {
mService.sendNotification(notificationId, notificationType);
}
+
+ public void registerNetworkCallback(INetworkCallback cb) throws RemoteException {
+ mService.registerNetworkCallback(cb);
+ }
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
index 90a3ce4..7d91574 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java
@@ -17,6 +17,7 @@
package com.android.cts.net.hostside;
import android.content.Intent;
+import android.net.ProxyInfo;
import android.net.VpnService;
import android.os.ParcelFileDescriptor;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -32,6 +33,10 @@
private static String TAG = "MyVpnService";
private static int MTU = 1799;
+ public static final String ACTION_ESTABLISHED = "com.android.cts.net.hostside.ESTABNLISHED";
+ public static final String EXTRA_ALWAYS_ON = "is-always-on";
+ public static final String EXTRA_LOCKDOWN_ENABLED = "is-lockdown-enabled";
+
private ParcelFileDescriptor mFd = null;
private PacketReflector mPacketReflector = null;
@@ -113,6 +118,8 @@
}
}
+ ProxyInfo vpnProxy = intent.getParcelableExtra(packageName + ".httpProxy");
+ builder.setHttpProxy(vpnProxy);
builder.setMtu(MTU);
builder.setBlocking(true);
builder.setSession("MyVpnService");
@@ -126,10 +133,19 @@
mFd = builder.establish();
Log.i(TAG, "Established, fd=" + (mFd == null ? "null" : mFd.getFd()));
+ broadcastEstablished();
+
mPacketReflector = new PacketReflector(mFd.getFileDescriptor(), MTU);
mPacketReflector.start();
}
+ private void broadcastEstablished() {
+ final Intent bcIntent = new Intent(ACTION_ESTABLISHED);
+ bcIntent.putExtra(EXTRA_ALWAYS_ON, isAlwaysOn());
+ bcIntent.putExtra(EXTRA_LOCKDOWN_ENABLED, isLockdownEnabled());
+ sendBroadcast(bcIntent);
+ }
+
private void stop() {
if (mPacketReflector != null) {
mPacketReflector.interrupt();
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
new file mode 100644
index 0000000..24dde9d
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net.hostside;
+
+import android.net.Network;
+
+import java.util.Objects;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class NetworkCallbackTest extends AbstractRestrictBackgroundNetworkTestCase {
+
+ private boolean mIsDataSaverSupported;
+ private Network mNetwork;
+ private final TestNetworkCallback mTestNetworkCallback = new TestNetworkCallback();
+
+ enum CallbackState {
+ NONE,
+ AVAILABLE,
+ LOST,
+ BLOCKED_STATUS
+ }
+
+ private static class CallbackInfo {
+ public final CallbackState state;
+ public final Network network;
+ public final Object arg;
+
+ CallbackInfo(CallbackState s, Network n, Object o) {
+ state = s; network = n; arg = o;
+ }
+
+ public String toString() {
+ return String.format("%s (%s) (%s)", state, network, arg);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof CallbackInfo)) return false;
+ // Ignore timeMs, since it's unpredictable.
+ final CallbackInfo other = (CallbackInfo) o;
+ return (state == other.state) && Objects.equals(network, other.network)
+ && Objects.equals(arg, other.arg);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(state, network, arg);
+ }
+ }
+
+ private class TestNetworkCallback extends INetworkCallback.Stub {
+ private static final int TEST_CALLBACK_TIMEOUT_MS = 200;
+
+ private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
+
+ protected void setLastCallback(CallbackState state, Network network, Object o) {
+ mCallbacks.offer(new CallbackInfo(state, network, o));
+ }
+
+ CallbackInfo nextCallback(int timeoutMs) {
+ CallbackInfo cb = null;
+ try {
+ cb = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ }
+ if (cb == null) {
+ fail("Did not receive callback after " + timeoutMs + "ms");
+ }
+ return cb;
+ }
+
+ CallbackInfo expectCallback(CallbackState state, Network expectedNetwork, Object o) {
+ final CallbackInfo expected = new CallbackInfo(state, expectedNetwork, o);
+ final CallbackInfo actual = nextCallback(TEST_CALLBACK_TIMEOUT_MS);
+ assertEquals("Unexpected callback:", expected, actual);
+ return actual;
+ }
+
+ @Override
+ public void onAvailable(Network network) {
+ setLastCallback(CallbackState.AVAILABLE, network, null);
+ }
+
+ @Override
+ public void onLost(Network network) {
+ setLastCallback(CallbackState.LOST, network, null);
+ }
+
+ @Override
+ public void onBlockedStatusChanged(Network network, boolean blocked) {
+ setLastCallback(CallbackState.BLOCKED_STATUS, network, blocked);
+ }
+
+ public void expectLostCallback(Network expectedNetwork) {
+ expectCallback(CallbackState.LOST, expectedNetwork, null);
+ }
+
+ public void expectAvailableCallback(Network expectedNetwork) {
+ expectCallback(CallbackState.AVAILABLE, expectedNetwork, null);
+ }
+
+ public void expectBlockedStatusCallback(Network expectedNetwork, boolean expectBlocked) {
+ expectCallback(CallbackState.BLOCKED_STATUS, expectedNetwork,
+ expectBlocked);
+ }
+
+ void assertNoCallback() {
+ CallbackInfo cb = null;
+ try {
+ cb = mCallbacks.poll(TEST_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // Expected.
+ }
+ if (cb != null) {
+ assertNull("Unexpected callback: " + cb, cb);
+ }
+ }
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mIsDataSaverSupported = isDataSaverSupported();
+
+ mNetwork = mCm.getActiveNetwork();
+
+ // Set initial state.
+ setBatterySaverMode(false);
+ registerBroadcastReceiver();
+
+ if (!mIsDataSaverSupported) return;
+ setRestrictBackground(false);
+ removeRestrictBackgroundWhitelist(mUid);
+ removeRestrictBackgroundBlacklist(mUid);
+ assertRestrictBackgroundChangedReceived(0);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ if (!mIsDataSaverSupported) return;
+
+ try {
+ resetMeteredNetwork();
+ } finally {
+ setRestrictBackground(false);
+ }
+ }
+
+ public void testOnBlockedStatusChanged_data_saver() throws Exception {
+ if (!mIsDataSaverSupported) return;
+
+ // Prepare metered wifi
+ if (!setMeteredNetwork()) return;
+
+ // Register callback
+ registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
+ mTestNetworkCallback.expectAvailableCallback(mNetwork);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+ // Enable restrict background
+ setRestrictBackground(true);
+ assertBackgroundNetworkAccess(false);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+
+ // Add to whitelist
+ addRestrictBackgroundWhitelist(mUid);
+ assertBackgroundNetworkAccess(true);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+ // Remove from whitelist
+ removeRestrictBackgroundWhitelist(mUid);
+ assertBackgroundNetworkAccess(false);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+
+ // Set to non-metered network
+ setUnmeteredNetwork();
+ assertBackgroundNetworkAccess(true);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+ // Disable restrict background, should not trigger callback
+ setRestrictBackground(false);
+ assertBackgroundNetworkAccess(true);
+ mTestNetworkCallback.assertNoCallback();
+ }
+
+
+ public void testOnBlockedStatusChanged_power_saver() throws Exception {
+ // Prepare metered wifi
+ if (!setMeteredNetwork()) return;
+
+ // Register callback
+ registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
+ mTestNetworkCallback.expectAvailableCallback(mNetwork);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+ // Enable Power Saver
+ setBatterySaverMode(true);
+ assertBackgroundNetworkAccess(false);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+
+ // Disable Power Saver
+ setBatterySaverMode(false);
+ assertBackgroundNetworkAccess(true);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+ // Set to non-metered network
+ setUnmeteredNetwork();
+ mTestNetworkCallback.assertNoCallback();
+
+ // Enable Power Saver
+ setBatterySaverMode(true);
+ assertBackgroundNetworkAccess(false);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+
+ // Disable Power Saver
+ setBatterySaverMode(false);
+ assertBackgroundNetworkAccess(true);
+ mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+ }
+
+ // TODO: 1. test against VPN lockdown.
+ // 2. test against multiple networks.
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java
index a4a2956..124c2c3 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/PacketReflector.java
@@ -16,6 +16,11 @@
package com.android.cts.net.hostside;
+import static android.system.OsConstants.ICMP6_ECHO_REPLY;
+import static android.system.OsConstants.ICMP6_ECHO_REQUEST;
+import static android.system.OsConstants.ICMP_ECHO;
+import static android.system.OsConstants.ICMP_ECHOREPLY;
+
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
@@ -47,8 +52,6 @@
private static final byte ICMP_ECHO = 8;
private static final byte ICMP_ECHOREPLY = 0;
- private static final byte ICMPV6_ECHO_REQUEST = (byte) 128;
- private static final byte ICMPV6_ECHO_REPLY = (byte) 129;
private static String TAG = "PacketReflector";
@@ -125,7 +128,7 @@
byte type = buf[hdrLen];
if (!(version == 4 && type == ICMP_ECHO) &&
- !(version == 6 && type == ICMPV6_ECHO_REQUEST)) {
+ !(version == 6 && type == (byte) ICMP6_ECHO_REQUEST)) {
return;
}
@@ -145,10 +148,18 @@
return;
}
+ byte replyType = buf[hdrLen];
+ if ((type == ICMP_ECHO && replyType != ICMP_ECHOREPLY)
+ || (type == (byte) ICMP6_ECHO_REQUEST && replyType != (byte) ICMP6_ECHO_REPLY)) {
+ Log.i(TAG, "Received unexpected ICMP reply: original " + type
+ + ", reply " + replyType);
+ return;
+ }
+
// Compare the response we got with the original packet.
// The only thing that should have changed are addresses, type and checksum.
// Overwrite them with the received bytes and see if the packet is otherwise identical.
- request[hdrLen] = buf[hdrLen]; // Type.
+ request[hdrLen] = buf[hdrLen]; // Type
request[hdrLen + 2] = buf[hdrLen + 2]; // Checksum byte 1.
request[hdrLen + 3] = buf[hdrLen + 3]; // Checksum byte 2.
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index bc982ce..17e1347 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -16,8 +16,10 @@
package com.android.cts.net.hostside;
+import static android.os.Process.INVALID_UID;
import static android.system.OsConstants.*;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
@@ -26,34 +28,31 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import android.net.Proxy;
+import android.net.ProxyInfo;
import android.net.VpnService;
import android.os.ParcelFileDescriptor;
import android.os.Process;
+import android.os.SystemProperties;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.UiScrollable;
import android.support.test.uiautomator.UiSelector;
import android.system.ErrnoException;
import android.system.Os;
+import android.system.OsConstants;
import android.system.StructPollfd;
import android.test.InstrumentationTestCase;
import android.test.MoreAsserts;
import android.text.TextUtils;
import android.util.Log;
-import com.android.cts.net.hostside.IRemoteSocketFactory;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
-import java.io.BufferedReader;
import java.io.Closeable;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.FileInputStream;
-import java.io.InputStreamReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.io.PrintWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet6Address;
@@ -61,9 +60,9 @@
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
-import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.util.Random;
+import java.util.concurrent.TimeUnit;
/**
* Tests for the VpnService API.
@@ -194,9 +193,8 @@
}
private void startVpn(
- String[] addresses, String[] routes,
- String allowedApplications, String disallowedApplications) throws Exception {
-
+ String[] addresses, String[] routes, String allowedApplications,
+ String disallowedApplications, ProxyInfo proxyInfo) throws Exception {
prepareVpn();
// Register a callback so we will be notified when our VPN comes up.
@@ -222,7 +220,9 @@
.putExtra(mPackageName + ".addresses", TextUtils.join(",", addresses))
.putExtra(mPackageName + ".routes", TextUtils.join(",", routes))
.putExtra(mPackageName + ".allowedapplications", allowedApplications)
- .putExtra(mPackageName + ".disallowedapplications", disallowedApplications);
+ .putExtra(mPackageName + ".disallowedapplications", disallowedApplications)
+ .putExtra(mPackageName + ".httpProxy", proxyInfo);
+
mActivity.startService(intent);
synchronized (mLock) {
if (mNetwork == null) {
@@ -353,7 +353,7 @@
MoreAsserts.assertEquals(data, read);
}
- private static void checkTcpReflection(String to, String expectedFrom) throws IOException {
+ private void checkTcpReflection(String to, String expectedFrom) throws IOException {
// Exercise TCP over the VPN by "connecting to ourselves". We open a server socket and a
// client socket, and connect the client socket to a remote host, with the port of the
// server socket. The PacketReflector reflects the packets, changing the source addresses
@@ -391,7 +391,8 @@
// Accept the connection on the server side.
listen.setSoTimeout(SOCKET_TIMEOUT_MS);
server = listen.accept();
-
+ checkConnectionOwnerUidTcp(client);
+ checkConnectionOwnerUidTcp(server);
// Check that the source and peer addresses are as expected.
assertEquals(expectedFrom, client.getLocalAddress().getHostAddress());
assertEquals(expectedFrom, server.getLocalAddress().getHostAddress());
@@ -424,7 +425,23 @@
}
}
- private static void checkUdpEcho(String to, String expectedFrom) throws IOException {
+ private void checkConnectionOwnerUidUdp(DatagramSocket s, boolean expectSuccess) {
+ final int expectedUid = expectSuccess ? Process.myUid() : INVALID_UID;
+ InetSocketAddress loc = new InetSocketAddress(s.getLocalAddress(), s.getLocalPort());
+ InetSocketAddress rem = new InetSocketAddress(s.getInetAddress(), s.getPort());
+ int uid = mCM.getConnectionOwnerUid(OsConstants.IPPROTO_UDP, loc, rem);
+ assertEquals(expectedUid, uid);
+ }
+
+ private void checkConnectionOwnerUidTcp(Socket s) {
+ final int expectedUid = Process.myUid();
+ InetSocketAddress loc = new InetSocketAddress(s.getLocalAddress(), s.getLocalPort());
+ InetSocketAddress rem = new InetSocketAddress(s.getInetAddress(), s.getPort());
+ int uid = mCM.getConnectionOwnerUid(OsConstants.IPPROTO_TCP, loc, rem);
+ assertEquals(expectedUid, uid);
+ }
+
+ private void checkUdpEcho(String to, String expectedFrom) throws IOException {
DatagramSocket s;
InetAddress address = InetAddress.getByName(to);
if (address instanceof Inet6Address) { // http://b/18094870
@@ -448,6 +465,7 @@
try {
if (expectedFrom != null) {
s.send(p);
+ checkConnectionOwnerUidUdp(s, true);
s.receive(p);
MoreAsserts.assertEquals(data, p.getData());
} else {
@@ -455,7 +473,9 @@
s.send(p);
s.receive(p);
fail("Received unexpected reply");
- } catch(IOException expected) {}
+ } catch (IOException expected) {
+ checkConnectionOwnerUidUdp(s, false);
+ }
}
} finally {
s.close();
@@ -537,16 +557,36 @@
public void testDefault() throws Exception {
if (!supportedHardware()) return;
+ // If adb TCP port opened, this test may running by adb over network.
+ // All of socket would be destroyed in this test. So this test don't
+ // support adb over network, see b/119382723.
+ if (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1
+ || SystemProperties.getInt("service.adb.tcp.port", -1) > -1) {
+ Log.i(TAG, "adb is running over the network, so skip this test");
+ return;
+ }
+
+ final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(
+ getInstrumentation().getTargetContext(), MyVpnService.ACTION_ESTABLISHED);
+ receiver.register();
FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
new String[] {"0.0.0.0/0", "::/0"},
- "", "");
+ "", "", null);
+
+ final Intent intent = receiver.awaitForBroadcast(TimeUnit.MINUTES.toMillis(1));
+ assertNotNull("Failed to receive broadcast from VPN service", intent);
+ assertFalse("Wrong VpnService#isAlwaysOn",
+ intent.getBooleanExtra(MyVpnService.EXTRA_ALWAYS_ON, true));
+ assertFalse("Wrong VpnService#isLockdownEnabled",
+ intent.getBooleanExtra(MyVpnService.EXTRA_LOCKDOWN_ENABLED, true));
assertSocketClosed(fd, TEST_HOST);
checkTrafficOnVpn();
+ receiver.unregisterQuietly();
}
public void testAppAllowed() throws Exception {
@@ -554,10 +594,11 @@
FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
+ // Shell app must not be put in here or it would kill the ADB-over-network use case
String allowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
new String[] {"192.0.2.0/24", "2001:db8::/32"},
- allowedApps, "");
+ allowedApps, "", null);
assertSocketClosed(fd, TEST_HOST);
@@ -571,13 +612,188 @@
FileDescriptor remoteFd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
String disallowedApps = mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
+ // If adb TCP port opened, this test may running by adb over TCP.
+ // Add com.android.shell appllication into blacklist to exclude adb socket for VPN test,
+ // see b/119382723.
+ // Note: The test don't support running adb over network for root device
+ disallowedApps = disallowedApps + ",com.android.shell";
+ Log.i(TAG, "Append shell app to disallowedApps: " + disallowedApps);
startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
new String[] {"192.0.2.0/24", "2001:db8::/32"},
- "", disallowedApps);
+ "", disallowedApps, null);
assertSocketStillOpen(localFd, TEST_HOST);
assertSocketStillOpen(remoteFd, TEST_HOST);
checkNoTrafficOnVpn();
}
+
+ public void testGetConnectionOwnerUidSecurity() throws Exception {
+ if (!supportedHardware()) return;
+
+ DatagramSocket s;
+ InetAddress address = InetAddress.getByName("localhost");
+ s = new DatagramSocket();
+ s.setSoTimeout(SOCKET_TIMEOUT_MS);
+ s.connect(address, 7);
+ InetSocketAddress loc = new InetSocketAddress(s.getLocalAddress(), s.getLocalPort());
+ InetSocketAddress rem = new InetSocketAddress(s.getInetAddress(), s.getPort());
+ try {
+ int uid = mCM.getConnectionOwnerUid(OsConstants.IPPROTO_TCP, loc, rem);
+ fail("Only an active VPN app may call this API.");
+ } catch (SecurityException expected) {
+ return;
+ }
+ }
+
+ public void testSetProxy() throws Exception {
+ if (!supportedHardware()) return;
+ ProxyInfo initialProxy = mCM.getDefaultProxy();
+ // Receiver for the proxy change broadcast.
+ BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
+ proxyBroadcastReceiver.register();
+
+ String allowedApps = mPackageName;
+ ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("10.0.0.1", 8888);
+ startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+ new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "",
+ testProxyInfo);
+
+ // Check that the proxy change broadcast is received
+ try {
+ assertNotNull("No proxy change was broadcast when VPN is connected.",
+ proxyBroadcastReceiver.awaitForBroadcast());
+ } finally {
+ proxyBroadcastReceiver.unregisterQuietly();
+ }
+
+ // Proxy is set correctly in network and in link properties.
+ assertNetworkHasExpectedProxy(testProxyInfo, mNetwork);
+ assertDefaultProxy(testProxyInfo);
+
+ proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
+ proxyBroadcastReceiver.register();
+ stopVpn();
+ try {
+ assertNotNull("No proxy change was broadcast when VPN was disconnected.",
+ proxyBroadcastReceiver.awaitForBroadcast());
+ } finally {
+ proxyBroadcastReceiver.unregisterQuietly();
+ }
+
+ // After disconnecting from VPN, the proxy settings are the ones of the initial network.
+ assertDefaultProxy(initialProxy);
+ }
+
+ public void testSetProxyDisallowedApps() throws Exception {
+ if (!supportedHardware()) return;
+ ProxyInfo initialProxy = mCM.getDefaultProxy();
+
+ // If adb TCP port opened, this test may running by adb over TCP.
+ // Add com.android.shell appllication into blacklist to exclude adb socket for VPN test,
+ // see b/119382723.
+ // Note: The test don't support running adb over network for root device
+ String disallowedApps = mPackageName + ",com.android.shell";
+ ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("10.0.0.1", 8888);
+ startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+ new String[] {"0.0.0.0/0", "::/0"}, "", disallowedApps,
+ testProxyInfo);
+
+ // The disallowed app does has the proxy configs of the default network.
+ assertNetworkHasExpectedProxy(initialProxy, mCM.getActiveNetwork());
+ assertDefaultProxy(initialProxy);
+ }
+
+ public void testNoProxy() throws Exception {
+ if (!supportedHardware()) return;
+ ProxyInfo initialProxy = mCM.getDefaultProxy();
+ BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
+ proxyBroadcastReceiver.register();
+ String allowedApps = mPackageName;
+ startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+ new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "", null);
+
+ try {
+ assertNotNull("No proxy change was broadcast.",
+ proxyBroadcastReceiver.awaitForBroadcast());
+ } finally {
+ proxyBroadcastReceiver.unregisterQuietly();
+ }
+
+ // The VPN network has no proxy set.
+ assertNetworkHasExpectedProxy(null, mNetwork);
+
+ proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
+ proxyBroadcastReceiver.register();
+ stopVpn();
+ try {
+ assertNotNull("No proxy change was broadcast.",
+ proxyBroadcastReceiver.awaitForBroadcast());
+ } finally {
+ proxyBroadcastReceiver.unregisterQuietly();
+ }
+ // After disconnecting from VPN, the proxy settings are the ones of the initial network.
+ assertDefaultProxy(initialProxy);
+ assertNetworkHasExpectedProxy(initialProxy, mCM.getActiveNetwork());
+ }
+
+ public void testBindToNetworkWithProxy() throws Exception {
+ if (!supportedHardware()) return;
+ String allowedApps = mPackageName;
+ Network initialNetwork = mCM.getActiveNetwork();
+ ProxyInfo initialProxy = mCM.getDefaultProxy();
+ ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("10.0.0.1", 8888);
+ // Receiver for the proxy change broadcast.
+ BlockingBroadcastReceiver proxyBroadcastReceiver = new ProxyChangeBroadcastReceiver();
+ proxyBroadcastReceiver.register();
+ startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+ new String[] {"0.0.0.0/0", "::/0"}, allowedApps, "",
+ testProxyInfo);
+
+ assertDefaultProxy(testProxyInfo);
+ mCM.bindProcessToNetwork(initialNetwork);
+ try {
+ assertNotNull("No proxy change was broadcast.",
+ proxyBroadcastReceiver.awaitForBroadcast());
+ } finally {
+ proxyBroadcastReceiver.unregisterQuietly();
+ }
+ assertDefaultProxy(initialProxy);
+ }
+
+ private void assertDefaultProxy(ProxyInfo expected) {
+ assertEquals("Incorrect proxy config.", expected, mCM.getDefaultProxy());
+ String expectedHost = expected == null ? null : expected.getHost();
+ String expectedPort = expected == null ? null : String.valueOf(expected.getPort());
+ assertEquals("Incorrect proxy host system property.", expectedHost,
+ System.getProperty("http.proxyHost"));
+ assertEquals("Incorrect proxy port system property.", expectedPort,
+ System.getProperty("http.proxyPort"));
+ }
+
+ private void assertNetworkHasExpectedProxy(ProxyInfo expected, Network network) {
+ LinkProperties lp = mCM.getLinkProperties(network);
+ assertNotNull("The network link properties object is null.", lp);
+ assertEquals("Incorrect proxy config.", expected, lp.getHttpProxy());
+
+ assertEquals(expected, mCM.getProxyForNetwork(network));
+ }
+
+ class ProxyChangeBroadcastReceiver extends BlockingBroadcastReceiver {
+ private boolean received;
+
+ public ProxyChangeBroadcastReceiver() {
+ super(VpnTest.this.getInstrumentation().getContext(), Proxy.PROXY_CHANGE_ACTION);
+ received = false;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!received) {
+ // Do not call onReceive() more than once.
+ super.onReceive(context, intent);
+ }
+ received = true;
+ }
+ }
}
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
index 2496c4a..ec536af 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
@@ -16,6 +16,7 @@
package com.android.cts.net.hostside.app2;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
+
import static com.android.cts.net.hostside.app2.Common.ACTION_RECEIVER_READY;
import static com.android.cts.net.hostside.app2.Common.DYNAMIC_RECEIVER;
import static com.android.cts.net.hostside.app2.Common.TAG;
@@ -26,13 +27,16 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
import android.os.IBinder;
-import android.os.Looper;
+import android.os.RemoteException;
import android.util.Log;
-import android.widget.Toast;
import com.android.cts.net.hostside.IMyService;
+import com.android.cts.net.hostside.INetworkCallback;
/**
* Service used to dynamically register a broadcast receiver.
@@ -40,7 +44,10 @@
public class MyService extends Service {
private static final String NOTIFICATION_CHANNEL_ID = "MyService";
+ ConnectivityManager mCm;
+
private MyBroadcastReceiver mReceiver;
+ private ConnectivityManager.NetworkCallback mNetworkCallback;
// TODO: move MyBroadcast static functions here - they were kept there to make git diff easier.
@@ -81,8 +88,67 @@
MyBroadcastReceiver .sendNotification(getApplicationContext(), NOTIFICATION_CHANNEL_ID,
notificationId, notificationType);
}
+
+ @Override
+ public void registerNetworkCallback(INetworkCallback cb) {
+ if (mNetworkCallback != null) {
+ Log.d(TAG, "unregister previous network callback: " + mNetworkCallback);
+ unregisterNetworkCallback();
+ }
+ Log.d(TAG, "registering network callback");
+
+ mNetworkCallback = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onBlockedStatusChanged(Network network, boolean blocked) {
+ try {
+ cb.onBlockedStatusChanged(network, blocked);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Cannot send onBlockedStatusChanged: " + e);
+ unregisterNetworkCallback();
+ }
+ }
+
+ @Override
+ public void onAvailable(Network network) {
+ try {
+ cb.onAvailable(network);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Cannot send onAvailable: " + e);
+ unregisterNetworkCallback();
+ }
+ }
+
+ @Override
+ public void onLost(Network network) {
+ try {
+ cb.onLost(network);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Cannot send onLost: " + e);
+ unregisterNetworkCallback();
+ }
+ }
+ };
+ mCm.registerNetworkCallback(makeWifiNetworkRequest(), mNetworkCallback);
+ try {
+ cb.asBinder().linkToDeath(() -> unregisterNetworkCallback(), 0);
+ } catch (RemoteException e) {
+ unregisterNetworkCallback();
+ }
+ }
};
+ private void unregisterNetworkCallback() {
+ Log.d(TAG, "unregistering network callback");
+ mCm.unregisterNetworkCallback(mNetworkCallback);
+ mNetworkCallback = null;
+ }
+
+ private NetworkRequest makeWifiNetworkRequest() {
+ return new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ }
+
@Override
public IBinder onBind(Intent intent) {
return mBinder;
@@ -94,6 +160,8 @@
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT));
+ mCm = (ConnectivityManager) getApplicationContext()
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
}
@Override
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
new file mode 100644
index 0000000..8d6c4ac
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkCallbackTests.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.net;
+public class HostsideNetworkCallbackTests extends HostsideNetworkTestCase {
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ uninstallPackage(TEST_APP2_PKG, false);
+ installPackage(TEST_APP2_APK);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ uninstallPackage(TEST_APP2_PKG, true);
+ }
+
+ public void testOnBlockedStatusChanged_data_saver() throws Exception {
+ runDeviceTests(TEST_PKG,
+ TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_data_saver");
+ }
+
+ public void testOnBlockedStatusChanged_power_saver() throws Exception {
+ runDeviceTests(TEST_PKG,
+ TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_power_saver");
+ }
+}
+
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
index 69b07af..e34ee89 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
@@ -44,4 +44,24 @@
public void testAppDisallowed() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testAppDisallowed");
}
+
+ public void testGetConnectionOwnerUidSecurity() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testGetConnectionOwnerUidSecurity");
+ }
+
+ public void testSetProxy() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetProxy");
+ }
+
+ public void testSetProxyDisallowedApps() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetProxyDisallowedApps");
+ }
+
+ public void testNoProxy() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testNoProxy");
+ }
+
+ public void testBindToNetworkWithProxy() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBindToNetworkWithProxy");
+ }
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java b/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
new file mode 100644
index 0000000..19e61c6
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.IDeviceTest;
+
+import java.lang.Integer;
+import java.lang.String;
+import java.util.Arrays;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Host-side tests for values in /proc/net.
+ *
+ * These tests analyze /proc/net to verify that certain networking properties are correct.
+ */
+public class ProcNetTest extends DeviceTestCase implements IBuildReceiver, IDeviceTest {
+ private static final String SPI_TIMEOUT_SYSCTL = "/proc/sys/net/core/xfrm_acq_expires";
+ private static final int MIN_ACQ_EXPIRES = 3600;
+ // Global sysctls. Must be present and set to 1.
+ private static final String[] GLOBAL_SYSCTLS = {
+ "/proc/sys/net/ipv4/fwmark_reflect",
+ "/proc/sys/net/ipv6/fwmark_reflect",
+ "/proc/sys/net/ipv4/tcp_fwmark_accept",
+ };
+
+ // Per-interface IPv6 autoconf sysctls.
+ private static final String IPV6_SYSCTL_DIR = "/proc/sys/net/ipv6/conf";
+ private static final String AUTOCONF_SYSCTL = "accept_ra_rt_table";
+
+ // Expected values for MIN|MAX_PLEN.
+ private static final String ACCEPT_RA_RT_INFO_MIN_PLEN_STRING = "accept_ra_rt_info_min_plen";
+ private static final int ACCEPT_RA_RT_INFO_MIN_PLEN_VALUE = 48;
+ private static final String ACCEPT_RA_RT_INFO_MAX_PLEN_STRING = "accept_ra_rt_info_max_plen";
+ private static final int ACCEPT_RA_RT_INFO_MAX_PLEN_VALUE = 64;
+ // Expected values for RFC 7559 router soliciations.
+ // Maximum number of router solicitations to send. -1 means no limit.
+ private static final int IPV6_WIFI_ROUTER_SOLICITATIONS = -1;
+ private ITestDevice mDevice;
+ private IBuildInfo mBuild;
+ private String[] mSysctlDirs;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setBuild(IBuildInfo build) {
+ mBuild = build;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setDevice(ITestDevice device) {
+ super.setDevice(device);
+ mDevice = device;
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mSysctlDirs = getSysctlDirs();
+ }
+
+ private String[] getSysctlDirs() throws Exception {
+ String interfaceDirs[] = mDevice.executeAdbCommand("shell", "ls", "-1",
+ IPV6_SYSCTL_DIR).split("\n");
+ List<String> interfaceDirsList = new ArrayList<String>(Arrays.asList(interfaceDirs));
+ interfaceDirsList.remove("all");
+ interfaceDirsList.remove("lo");
+ return interfaceDirsList.toArray(new String[interfaceDirsList.size()]);
+ }
+
+
+ protected void assertLess(String sysctl, int a, int b) {
+ assertTrue("value of " + sysctl + ": expected < " + b + " but was: " + a, a < b);
+ }
+
+ protected void assertAtLeast(String sysctl, int a, int b) {
+ assertTrue("value of " + sysctl + ": expected >= " + b + " but was: " + a, a >= b);
+ }
+
+ public int readIntFromPath(String path) throws Exception {
+ String mode = mDevice.executeAdbCommand("shell", "stat", "-c", "%a", path).trim();
+ String user = mDevice.executeAdbCommand("shell", "stat", "-c", "%u", path).trim();
+ String group = mDevice.executeAdbCommand("shell", "stat", "-c", "%g", path).trim();
+ assertEquals(mode, "644");
+ assertEquals(user, "0");
+ assertEquals(group, "0");
+ return Integer.parseInt(mDevice.executeAdbCommand("shell", "cat", path).trim());
+ }
+
+ /**
+ * Checks that SPI default timeouts are overridden, and set to a reasonable length of time
+ */
+ public void testMinAcqExpires() throws Exception {
+ int value = readIntFromPath(SPI_TIMEOUT_SYSCTL);
+ assertAtLeast(SPI_TIMEOUT_SYSCTL, value, MIN_ACQ_EXPIRES);
+ }
+
+ /**
+ * Checks that the sysctls for multinetwork kernel features are present and
+ * enabled.
+ */
+ public void testProcSysctls() throws Exception {
+ for (String sysctl : GLOBAL_SYSCTLS) {
+ int value = readIntFromPath(sysctl);
+ assertEquals(sysctl, 1, value);
+ }
+
+ for (String interfaceDir : mSysctlDirs) {
+ String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + AUTOCONF_SYSCTL;
+ int value = readIntFromPath(path);
+ assertLess(path, value, 0);
+ }
+ }
+
+ /**
+ * Verify that accept_ra_rt_info_{min,max}_plen exists and is set to the expected value
+ */
+ public void testAcceptRaRtInfoMinMaxPlen() throws Exception {
+ for (String interfaceDir : mSysctlDirs) {
+ String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "accept_ra_rt_info_min_plen";
+ int value = readIntFromPath(path);
+ assertEquals(path, value, ACCEPT_RA_RT_INFO_MIN_PLEN_VALUE);
+ path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "accept_ra_rt_info_max_plen";
+ value = readIntFromPath(path);
+ assertEquals(path, value, ACCEPT_RA_RT_INFO_MAX_PLEN_VALUE);
+ }
+ }
+
+ /**
+ * Verify that router_solicitations exists and is set to the expected value
+ * and verify that router_solicitation_max_interval exists and is in an acceptable interval.
+ */
+ public void testRouterSolicitations() throws Exception {
+ for (String interfaceDir : mSysctlDirs) {
+ String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "router_solicitations";
+ int value = readIntFromPath(path);
+ assertEquals(IPV6_WIFI_ROUTER_SOLICITATIONS, value);
+ path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "router_solicitation_max_interval";
+ int interval = readIntFromPath(path);
+ final int lowerBoundSec = 15 * 60;
+ final int upperBoundSec = 60 * 60;
+ assertTrue(lowerBoundSec <= interval);
+ assertTrue(interval <= upperBoundSec);
+ }
+ }
+}
diff --git a/tests/cts/net/Android.mk b/tests/cts/net/Android.mk
index 45941a7..0497470 100644
--- a/tests/cts/net/Android.mk
+++ b/tests/cts/net/Android.mk
@@ -26,7 +26,6 @@
LOCAL_JAVA_LIBRARIES := \
voip-common \
- conscrypt \
org.apache.http.legacy \
android.test.base.stubs \
@@ -40,13 +39,16 @@
LOCAL_PACKAGE_NAME := CtsNetTestCases
LOCAL_STATIC_JAVA_LIBRARIES := \
+ FrameworksNetCommonTests \
core-tests-support \
compatibility-device-util-axt \
ctstestrunner-axt \
ctstestserver \
mockwebserver \
junit \
- truth-prebuilt
+ junit-params \
+ truth-prebuilt \
+
# uncomment when b/13249961 is fixed
#LOCAL_SDK_VERSION := current
diff --git a/tests/cts/net/AndroidTest.xml b/tests/cts/net/AndroidTest.xml
index 1326970..76ff167 100644
--- a/tests/cts/net/AndroidTest.xml
+++ b/tests/cts/net/AndroidTest.xml
@@ -15,6 +15,8 @@
<configuration description="Config for CTS Net test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
+ <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
+ <option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsNetTestCases.apk" />
diff --git a/tests/cts/net/jni/Android.mk b/tests/cts/net/jni/Android.mk
index 727a44d..ccb1278 100644
--- a/tests/cts/net/jni/Android.mk
+++ b/tests/cts/net/jni/Android.mk
@@ -36,7 +36,7 @@
LOCAL_MODULE := libnativemultinetwork_jni
# Don't include this package in any configuration by default.
LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := NativeMultinetworkJni.c
+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
diff --git a/tests/cts/net/jni/NativeDnsJni.c b/tests/cts/net/jni/NativeDnsJni.c
index 352c0c5..6d3d1c3 100644
--- a/tests/cts/net/jni/NativeDnsJni.c
+++ b/tests/cts/net/jni/NativeDnsJni.c
@@ -120,8 +120,8 @@
gai_strerror(res));
return JNI_FALSE;
}
- if (strstr(buf, "google.com") == NULL) {
- ALOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com: %s",
+ if (strstr(buf, "google.com") == NULL && strstr(buf, "dns.google") == NULL) {
+ ALOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
GoogleDNSIpV4Address, buf);
return JNI_FALSE;
}
@@ -133,8 +133,9 @@
res, gai_strerror(res));
return JNI_FALSE;
}
- if (strstr(buf, "google.com") == NULL) {
- ALOGD("getnameinfo(%s) didn't return google.com: %s", GoogleDNSIpV6Address2, buf);
+ if (strstr(buf, "google.com") == NULL && strstr(buf, "dns.google") == NULL) {
+ ALOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
+ GoogleDNSIpV6Address2, buf);
return JNI_FALSE;
}
diff --git a/tests/cts/net/jni/NativeMultinetworkJni.c b/tests/cts/net/jni/NativeMultinetworkJni.c
deleted file mode 100644
index 2fa5291..0000000
--- a/tests/cts/net/jni/NativeMultinetworkJni.c
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#define LOG_TAG "MultinetworkApiTest"
-#include <utils/Log.h>
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <inttypes.h>
-#include <jni.h>
-#include <netdb.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <android/multinetwork.h>
-
-#define UNUSED(X) ((void) (X))
-
-static const char kHostname[] = "connectivitycheck.android.com";
-
-
-JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runGetaddrinfoCheck(
- JNIEnv* env, jclass class, jlong nethandle) {
- UNUSED(env);
- UNUSED(class);
- net_handle_t handle = (net_handle_t) nethandle;
- struct addrinfo *res = NULL;
-
- errno = 0;
- int rval = android_getaddrinfofornetwork(handle, kHostname, NULL, NULL, &res);
- const int saved_errno = errno;
- freeaddrinfo(res);
-
- ALOGD("android_getaddrinfofornetwork(%" PRIu64 ", %s) returned rval=%d errno=%d",
- handle, kHostname, rval, saved_errno);
- return rval == 0 ? 0 : -saved_errno;
-}
-
-JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runSetprocnetwork(
- JNIEnv* env, jclass class, jlong nethandle) {
- UNUSED(env);
- UNUSED(class);
- net_handle_t handle = (net_handle_t) nethandle;
-
- errno = 0;
- int rval = android_setprocnetwork(handle);
- const int saved_errno = errno;
- ALOGD("android_setprocnetwork(%" PRIu64 ") returned rval=%d errno=%d",
- handle, rval, saved_errno);
- return rval == 0 ? 0 : -saved_errno;
-}
-
-JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runSetsocknetwork(
- JNIEnv* env, jclass class, jlong nethandle) {
- UNUSED(env);
- UNUSED(class);
- net_handle_t handle = (net_handle_t) nethandle;
-
- errno = 0;
- int fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
- if (fd < 0) {
- ALOGD("socket() failed, errno=%d", errno);
- return -errno;
- }
-
- errno = 0;
- int rval = android_setsocknetwork(handle, fd);
- const int saved_errno = errno;
- ALOGD("android_setprocnetwork(%" PRIu64 ", %d) returned rval=%d errno=%d",
- handle, fd, rval, saved_errno);
- close(fd);
- return rval == 0 ? 0 : -saved_errno;
-}
-
-// Use sizeof("x") - 1 because we need a compile-time constant, and strlen("x")
-// isn't guaranteed to fold to a constant.
-static const int kSockaddrStrLen = INET6_ADDRSTRLEN + sizeof("[]:65535") - 1;
-
-void sockaddr_ntop(const struct sockaddr *sa, socklen_t salen, char *dst, const size_t size) {
- char addrstr[INET6_ADDRSTRLEN];
- char portstr[sizeof("65535")];
- char buf[kSockaddrStrLen+1];
-
- int ret = getnameinfo(sa, salen,
- addrstr, sizeof(addrstr),
- portstr, sizeof(portstr),
- NI_NUMERICHOST | NI_NUMERICSERV);
- if (ret == 0) {
- snprintf(buf, sizeof(buf),
- (sa->sa_family == AF_INET6) ? "[%s]:%s" : "%s:%s",
- addrstr, portstr);
- } else {
- sprintf(buf, "???");
- }
-
- strlcpy(dst, buf, size);
-}
-
-JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runDatagramCheck(
- JNIEnv* env, jclass class, jlong nethandle) {
- UNUSED(env);
- UNUSED(class);
- const struct addrinfo kHints = {
- .ai_flags = AI_ADDRCONFIG,
- .ai_family = AF_UNSPEC,
- .ai_socktype = SOCK_DGRAM,
- .ai_protocol = IPPROTO_UDP,
- };
- struct addrinfo *res = NULL;
- net_handle_t handle = (net_handle_t) nethandle;
-
- static const char kPort[] = "443";
- int rval = android_getaddrinfofornetwork(handle, kHostname, kPort, &kHints, &res);
- if (rval != 0) {
- ALOGD("android_getaddrinfofornetwork(%llu, %s) returned rval=%d errno=%d",
- handle, kHostname, rval, errno);
- freeaddrinfo(res);
- return -errno;
- }
-
- // Rely upon getaddrinfo sorting the best destination to the front.
- int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
- if (fd < 0) {
- ALOGD("socket(%d, %d, %d) failed, errno=%d",
- res->ai_family, res->ai_socktype, res->ai_protocol, errno);
- freeaddrinfo(res);
- return -errno;
- }
-
- rval = android_setsocknetwork(handle, fd);
- ALOGD("android_setprocnetwork(%llu, %d) returned rval=%d errno=%d",
- handle, fd, rval, errno);
- if (rval != 0) {
- close(fd);
- freeaddrinfo(res);
- return -errno;
- }
-
- char addrstr[kSockaddrStrLen+1];
- sockaddr_ntop(res->ai_addr, res->ai_addrlen, addrstr, sizeof(addrstr));
- ALOGD("Attempting connect() to %s ...", addrstr);
-
- rval = connect(fd, res->ai_addr, res->ai_addrlen);
- if (rval != 0) {
- close(fd);
- freeaddrinfo(res);
- return -errno;
- }
- freeaddrinfo(res);
-
- struct sockaddr_storage src_addr;
- socklen_t src_addrlen = sizeof(src_addr);
- if (getsockname(fd, (struct sockaddr *)&src_addr, &src_addrlen) != 0) {
- close(fd);
- return -errno;
- }
- sockaddr_ntop((const struct sockaddr *)&src_addr, sizeof(src_addr), addrstr, sizeof(addrstr));
- ALOGD("... from %s", addrstr);
-
- // Don't let reads or writes block indefinitely.
- const struct timeval timeo = { 2, 0 }; // 2 seconds
- setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
- setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo));
-
- // For reference see:
- // https://tools.ietf.org/html/draft-tsvwg-quic-protocol-01#section-6.1
- uint8_t quic_packet[] = {
- 0x0c, // public flags: 64bit conn ID, 8bit sequence number
- 0, 0, 0, 0, 0, 0, 0, 0, // 64bit connection ID
- 0x01, // sequence number
- 0x00, // private flags
- 0x07, // type: regular frame type "PING"
- };
-
- arc4random_buf(quic_packet + 1, 8); // random connection ID
-
- uint8_t response[1500];
- ssize_t sent, rcvd;
- static const int MAX_RETRIES = 5;
- int i, errnum = 0;
-
- for (i = 0; i < MAX_RETRIES; i++) {
- sent = send(fd, quic_packet, sizeof(quic_packet), 0);
- if (sent < (ssize_t)sizeof(quic_packet)) {
- errnum = errno;
- ALOGD("send(QUIC packet) returned sent=%zd, errno=%d", sent, errnum);
- close(fd);
- return -errnum;
- }
-
- rcvd = recv(fd, response, sizeof(response), 0);
- if (rcvd > 0) {
- break;
- } else {
- errnum = errno;
- ALOGD("[%d/%d] recv(QUIC response) returned rcvd=%zd, errno=%d",
- i + 1, MAX_RETRIES, rcvd, errnum);
- }
- }
- if (rcvd < sent) {
- ALOGD("QUIC UDP %s: sent=%zd but rcvd=%zd, errno=%d", kPort, sent, rcvd, errnum);
- if (rcvd <= 0) {
- ALOGD("Does this network block UDP port %s?", kPort);
- }
- close(fd);
- return -EPROTO;
- }
-
- int conn_id_cmp = memcmp(quic_packet + 1, response + 1, 8);
- if (conn_id_cmp != 0) {
- ALOGD("sent and received connection IDs do not match");
- close(fd);
- return -EPROTO;
- }
-
- // TODO: log, and compare to the IP address encoded in the
- // response, since this should be a public reset packet.
-
- close(fd);
- return 0;
-}
diff --git a/tests/cts/net/jni/NativeMultinetworkJni.cpp b/tests/cts/net/jni/NativeMultinetworkJni.cpp
new file mode 100644
index 0000000..a6b5e90
--- /dev/null
+++ b/tests/cts/net/jni/NativeMultinetworkJni.cpp
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#define LOG_TAG "MultinetworkApiTest"
+#include <utils/Log.h>
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <jni.h>
+#include <netdb.h>
+#include <poll.h> /* poll */
+#include <resolv.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <string>
+
+#include <android/multinetwork.h>
+#include <nativehelper/JNIHelp.h>
+
+#define EXPECT_GE(env, actual, expected, msg) \
+ do { \
+ if (actual < expected) { \
+ jniThrowExceptionFmt(env, "java/lang/AssertionError", \
+ "%s:%d: %s EXPECT_GE: expected %d, got %d", \
+ __FILE__, __LINE__, msg, expected, actual); \
+ } \
+ } while (0)
+
+#define EXPECT_GT(env, actual, expected, msg) \
+ do { \
+ if (actual <= expected) { \
+ jniThrowExceptionFmt(env, "java/lang/AssertionError", \
+ "%s:%d: %s EXPECT_GT: expected %d, got %d", \
+ __FILE__, __LINE__, msg, expected, actual); \
+ } \
+ } while (0)
+
+#define EXPECT_EQ(env, expected, actual, msg) \
+ do { \
+ if (actual != expected) { \
+ jniThrowExceptionFmt(env, "java/lang/AssertionError", \
+ "%s:%d: %s EXPECT_EQ: expected %d, got %d", \
+ __FILE__, __LINE__, msg, expected, actual); \
+ } \
+ } while (0)
+
+static const int MAXPACKET = 8 * 1024;
+static const int TIMEOUT_MS = 15000;
+static const char kHostname[] = "connectivitycheck.android.com";
+static const char kNxDomainName[] = "test1-nx.metric.gstatic.com";
+static const char kGoogleName[] = "www.google.com";
+
+int makeQuery(const char* name, int qtype, uint8_t* buf, size_t buflen) {
+ return res_mkquery(ns_o_query, name, ns_c_in, qtype, NULL, 0, NULL, buf, buflen);
+}
+
+int getAsyncResponse(JNIEnv* env, int fd, int timeoutMs, int* rcode, uint8_t* buf, size_t bufLen) {
+ struct pollfd wait_fd = { .fd = fd, .events = POLLIN };
+
+ poll(&wait_fd, 1, timeoutMs);
+ if (wait_fd.revents & POLLIN) {
+ int n = android_res_nresult(fd, rcode, buf, bufLen);
+ // Verify that android_res_nresult() closed the fd
+ char dummy;
+ EXPECT_EQ(env, -1, read(fd, &dummy, sizeof(dummy)), "res_nresult check for closing fd");
+ EXPECT_EQ(env, EBADF, errno, "res_nresult check for errno");
+ return n;
+ }
+
+ return -ETIMEDOUT;
+}
+
+int extractIpAddressAnswers(uint8_t* buf, size_t bufLen, int family) {
+ ns_msg handle;
+ if (ns_initparse((const uint8_t*) buf, bufLen, &handle) < 0) {
+ return -errno;
+ }
+ const int ancount = ns_msg_count(handle, ns_s_an);
+ // Answer count = 0 is valid(e.g. response of query with root)
+ if (!ancount) {
+ return 0;
+ }
+ ns_rr rr;
+ bool hasValidAns = false;
+ for (int i = 0; i < ancount; i++) {
+ if (ns_parserr(&handle, ns_s_an, i, &rr) < 0) {
+ // If there is no valid answer, test will fail.
+ continue;
+ }
+ const uint8_t* rdata = ns_rr_rdata(rr);
+ char buffer[INET6_ADDRSTRLEN];
+ if (inet_ntop(family, (const char*) rdata, buffer, sizeof(buffer)) == NULL) {
+ return -errno;
+ }
+ hasValidAns = true;
+ }
+ return hasValidAns ? 0 : -EBADMSG;
+}
+
+int expectAnswersValid(JNIEnv* env, int fd, int family, int expectedRcode) {
+ int rcode = -1;
+ uint8_t buf[MAXPACKET] = {};
+ int res = getAsyncResponse(env, fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
+ if (res < 0) {
+ return res;
+ }
+
+ EXPECT_EQ(env, expectedRcode, rcode, "rcode is not expected");
+
+ if (expectedRcode == ns_r_noerror && res > 0) {
+ return extractIpAddressAnswers(buf, res, family);
+ }
+ return 0;
+}
+
+int expectAnswersNotValid(JNIEnv* env, int fd, int expectedErrno) {
+ int rcode = -1;
+ uint8_t buf[MAXPACKET] = {};
+ int res = getAsyncResponse(env, fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
+ if (res != expectedErrno) {
+ ALOGD("res:%d, expectedErrno = %d", res, expectedErrno);
+ return (res > 0) ? -EREMOTEIO : res;
+ }
+ return 0;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runResNqueryCheck(
+ JNIEnv* env, jclass, jlong nethandle) {
+ net_handle_t handle = (net_handle_t) nethandle;
+
+ // V4
+ int fd = android_res_nquery(handle, kHostname, ns_c_in, ns_t_a, 0);
+ EXPECT_GE(env, fd, 0, "v4 res_nquery");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
+ "v4 res_nquery check answers");
+
+ // V4 NXDOMAIN
+ fd = android_res_nquery(handle, kNxDomainName, ns_c_in, ns_t_a, 0);
+ EXPECT_GE(env, fd, 0, "v4 res_nquery NXDOMAIN");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_nxdomain),
+ "v4 res_nquery NXDOMAIN check answers");
+
+ // V6
+ fd = android_res_nquery(handle, kHostname, ns_c_in, ns_t_aaaa, 0);
+ EXPECT_GE(env, fd, 0, "v6 res_nquery");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
+ "v6 res_nquery check answers");
+
+ // V6 NXDOMAIN
+ fd = android_res_nquery(handle, kNxDomainName, ns_c_in, ns_t_aaaa, 0);
+ EXPECT_GE(env, fd, 0, "v6 res_nquery NXDOMAIN");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_nxdomain),
+ "v6 res_nquery NXDOMAIN check answers");
+
+ return 0;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runResNsendCheck(
+ JNIEnv* env, jclass, jlong nethandle) {
+ net_handle_t handle = (net_handle_t) nethandle;
+ // V4
+ uint8_t buf1[MAXPACKET] = {};
+
+ int len1 = makeQuery(kGoogleName, ns_t_a, buf1, sizeof(buf1));
+ EXPECT_GT(env, len1, 0, "v4 res_mkquery 1st");
+
+ uint8_t buf2[MAXPACKET] = {};
+ int len2 = makeQuery(kHostname, ns_t_a, buf2, sizeof(buf2));
+ EXPECT_GT(env, len2, 0, "v4 res_mkquery 2nd");
+
+ int fd1 = android_res_nsend(handle, buf1, len1, 0);
+ EXPECT_GE(env, fd1, 0, "v4 res_nsend 1st");
+ int fd2 = android_res_nsend(handle, buf2, len2, 0);
+ EXPECT_GE(env, fd2, 0, "v4 res_nsend 2nd");
+
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd2, AF_INET, ns_r_noerror),
+ "v4 res_nsend 2nd check answers");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET, ns_r_noerror),
+ "v4 res_nsend 1st check answers");
+
+ // V4 NXDOMAIN
+ memset(buf1, 0, sizeof(buf1));
+ len1 = makeQuery(kNxDomainName, ns_t_a, buf1, sizeof(buf1));
+ EXPECT_GT(env, len1, 0, "v4 res_mkquery NXDOMAIN");
+ fd1 = android_res_nsend(handle, buf1, len1, 0);
+ EXPECT_GE(env, fd1, 0, "v4 res_nsend NXDOMAIN");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET, ns_r_nxdomain),
+ "v4 res_nsend NXDOMAIN check answers");
+
+ // V6
+ memset(buf1, 0, sizeof(buf1));
+ memset(buf2, 0, sizeof(buf2));
+ len1 = makeQuery(kGoogleName, ns_t_aaaa, buf1, sizeof(buf1));
+ EXPECT_GT(env, len1, 0, "v6 res_mkquery 1st");
+ len2 = makeQuery(kHostname, ns_t_aaaa, buf2, sizeof(buf2));
+ EXPECT_GT(env, len2, 0, "v6 res_mkquery 2nd");
+
+ fd1 = android_res_nsend(handle, buf1, len1, 0);
+ EXPECT_GE(env, fd1, 0, "v6 res_nsend 1st");
+ fd2 = android_res_nsend(handle, buf2, len2, 0);
+ EXPECT_GE(env, fd2, 0, "v6 res_nsend 2nd");
+
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd2, AF_INET6, ns_r_noerror),
+ "v6 res_nsend 2nd check answers");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET6, ns_r_noerror),
+ "v6 res_nsend 1st check answers");
+
+ // V6 NXDOMAIN
+ memset(buf1, 0, sizeof(buf1));
+ len1 = makeQuery(kNxDomainName, ns_t_aaaa, buf1, sizeof(buf1));
+ EXPECT_GT(env, len1, 0, "v6 res_mkquery NXDOMAIN");
+ fd1 = android_res_nsend(handle, buf1, len1, 0);
+ EXPECT_GE(env, fd1, 0, "v6 res_nsend NXDOMAIN");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET6, ns_r_nxdomain),
+ "v6 res_nsend NXDOMAIN check answers");
+
+ return 0;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runResNcancelCheck(
+ JNIEnv* env, jclass, jlong nethandle) {
+ net_handle_t handle = (net_handle_t) nethandle;
+
+ int fd = android_res_nquery(handle, kGoogleName, ns_c_in, ns_t_a, 0);
+ errno = 0;
+ android_res_cancel(fd);
+ int err = errno;
+ EXPECT_EQ(env, 0, err, "res_cancel");
+ // DO NOT call cancel or result with the same fd more than once,
+ // otherwise it will hit fdsan double-close fd.
+ return 0;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runResNapiMalformedCheck(
+ JNIEnv* env, jclass, jlong nethandle) {
+ net_handle_t handle = (net_handle_t) nethandle;
+
+ // It is the equivalent of "dig . a", Query with an empty name.
+ int fd = android_res_nquery(handle, "", ns_c_in, ns_t_a, 0);
+ EXPECT_GE(env, fd, 0, "res_nquery root");
+ EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
+ "res_nquery root check answers");
+
+ // Label limit 63
+ std::string exceedingLabelQuery = "www." + std::string(70, 'g') + ".com";
+ // Name limit 255
+ std::string exceedingDomainQuery = "www." + std::string(255, 'g') + ".com";
+
+ fd = android_res_nquery(handle, exceedingLabelQuery.c_str(), ns_c_in, ns_t_a, 0);
+ EXPECT_EQ(env, -EMSGSIZE, fd, "res_nquery exceedingLabelQuery");
+ fd = android_res_nquery(handle, exceedingDomainQuery.c_str(), ns_c_in, ns_t_aaaa, 0);
+ EXPECT_EQ(env, -EMSGSIZE, fd, "res_nquery exceedingDomainQuery");
+
+ uint8_t buf[10] = {};
+ // empty BLOB
+ fd = android_res_nsend(handle, buf, 10, 0);
+ EXPECT_GE(env, fd, 0, "res_nsend empty BLOB");
+ EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
+ "res_nsend empty BLOB check answers");
+
+ uint8_t largeBuf[2 * MAXPACKET] = {};
+ // A buffer larger than 8KB
+ fd = android_res_nsend(handle, largeBuf, sizeof(largeBuf), 0);
+ EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend buffer larger than 8KB");
+
+ // 5000 bytes filled with 0. This returns EMSGSIZE because FrameworkListener limits the size of
+ // commands to 4096 bytes.
+ fd = android_res_nsend(handle, largeBuf, 5000, 0);
+ EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend 5000 bytes filled with 0");
+
+ // 500 bytes filled with 0
+ fd = android_res_nsend(handle, largeBuf, 500, 0);
+ EXPECT_GE(env, fd, 0, "res_nsend 500 bytes filled with 0");
+ EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
+ "res_nsend 500 bytes filled with 0 check answers");
+
+ // 5000 bytes filled with 0xFF
+ uint8_t ffBuf[5001] = {};
+ memset(ffBuf, 0xFF, sizeof(ffBuf));
+ ffBuf[5000] = '\0';
+ fd = android_res_nsend(handle, ffBuf, sizeof(ffBuf), 0);
+ EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend 5000 bytes filled with 0xFF");
+
+ // 500 bytes filled with 0xFF
+ ffBuf[500] = '\0';
+ fd = android_res_nsend(handle, ffBuf, 501, 0);
+ EXPECT_GE(env, fd, 0, "res_nsend 500 bytes filled with 0xFF");
+ EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
+ "res_nsend 500 bytes filled with 0xFF check answers");
+
+ return 0;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runGetaddrinfoCheck(
+ JNIEnv*, jclass, jlong nethandle) {
+ net_handle_t handle = (net_handle_t) nethandle;
+ struct addrinfo *res = NULL;
+
+ errno = 0;
+ int rval = android_getaddrinfofornetwork(handle, kHostname, NULL, NULL, &res);
+ const int saved_errno = errno;
+ freeaddrinfo(res);
+
+ ALOGD("android_getaddrinfofornetwork(%" PRIu64 ", %s) returned rval=%d errno=%d",
+ handle, kHostname, rval, saved_errno);
+ return rval == 0 ? 0 : -saved_errno;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runSetprocnetwork(
+ JNIEnv*, jclass, jlong nethandle) {
+ net_handle_t handle = (net_handle_t) nethandle;
+
+ errno = 0;
+ int rval = android_setprocnetwork(handle);
+ const int saved_errno = errno;
+ ALOGD("android_setprocnetwork(%" PRIu64 ") returned rval=%d errno=%d",
+ handle, rval, saved_errno);
+ return rval == 0 ? 0 : -saved_errno;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runSetsocknetwork(
+ JNIEnv*, jclass, jlong nethandle) {
+ net_handle_t handle = (net_handle_t) nethandle;
+
+ errno = 0;
+ int fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd < 0) {
+ ALOGD("socket() failed, errno=%d", errno);
+ return -errno;
+ }
+
+ errno = 0;
+ int rval = android_setsocknetwork(handle, fd);
+ const int saved_errno = errno;
+ ALOGD("android_setprocnetwork(%" PRIu64 ", %d) returned rval=%d errno=%d",
+ handle, fd, rval, saved_errno);
+ close(fd);
+ return rval == 0 ? 0 : -saved_errno;
+}
+
+// Use sizeof("x") - 1 because we need a compile-time constant, and strlen("x")
+// isn't guaranteed to fold to a constant.
+static const int kSockaddrStrLen = INET6_ADDRSTRLEN + sizeof("[]:65535") - 1;
+
+void sockaddr_ntop(const struct sockaddr *sa, socklen_t salen, char *dst, const size_t size) {
+ char addrstr[INET6_ADDRSTRLEN];
+ char portstr[sizeof("65535")];
+ char buf[kSockaddrStrLen+1];
+
+ int ret = getnameinfo(sa, salen,
+ addrstr, sizeof(addrstr),
+ portstr, sizeof(portstr),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (ret == 0) {
+ snprintf(buf, sizeof(buf),
+ (sa->sa_family == AF_INET6) ? "[%s]:%s" : "%s:%s",
+ addrstr, portstr);
+ } else {
+ sprintf(buf, "???");
+ }
+
+ strlcpy(dst, buf, size);
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runDatagramCheck(
+ JNIEnv*, jclass, jlong nethandle) {
+ const struct addrinfo kHints = {
+ .ai_flags = AI_ADDRCONFIG,
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_DGRAM,
+ .ai_protocol = IPPROTO_UDP,
+ };
+ struct addrinfo *res = NULL;
+ net_handle_t handle = (net_handle_t) nethandle;
+
+ static const char kPort[] = "443";
+ int rval = android_getaddrinfofornetwork(handle, kHostname, kPort, &kHints, &res);
+ if (rval != 0) {
+ ALOGD("android_getaddrinfofornetwork(%llu, %s) returned rval=%d errno=%d",
+ handle, kHostname, rval, errno);
+ freeaddrinfo(res);
+ return -errno;
+ }
+
+ // Rely upon getaddrinfo sorting the best destination to the front.
+ int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (fd < 0) {
+ ALOGD("socket(%d, %d, %d) failed, errno=%d",
+ res->ai_family, res->ai_socktype, res->ai_protocol, errno);
+ freeaddrinfo(res);
+ return -errno;
+ }
+
+ rval = android_setsocknetwork(handle, fd);
+ ALOGD("android_setprocnetwork(%llu, %d) returned rval=%d errno=%d",
+ handle, fd, rval, errno);
+ if (rval != 0) {
+ close(fd);
+ freeaddrinfo(res);
+ return -errno;
+ }
+
+ char addrstr[kSockaddrStrLen+1];
+ sockaddr_ntop(res->ai_addr, res->ai_addrlen, addrstr, sizeof(addrstr));
+ ALOGD("Attempting connect() to %s ...", addrstr);
+
+ rval = connect(fd, res->ai_addr, res->ai_addrlen);
+ if (rval != 0) {
+ close(fd);
+ freeaddrinfo(res);
+ return -errno;
+ }
+ freeaddrinfo(res);
+
+ struct sockaddr_storage src_addr;
+ socklen_t src_addrlen = sizeof(src_addr);
+ if (getsockname(fd, (struct sockaddr *)&src_addr, &src_addrlen) != 0) {
+ close(fd);
+ return -errno;
+ }
+ sockaddr_ntop((const struct sockaddr *)&src_addr, sizeof(src_addr), addrstr, sizeof(addrstr));
+ ALOGD("... from %s", addrstr);
+
+ // Don't let reads or writes block indefinitely.
+ const struct timeval timeo = { 2, 0 }; // 2 seconds
+ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo));
+
+ // For reference see:
+ // https://tools.ietf.org/html/draft-tsvwg-quic-protocol-01#section-6.1
+ uint8_t quic_packet[] = {
+ 0x0c, // public flags: 64bit conn ID, 8bit sequence number
+ 0, 0, 0, 0, 0, 0, 0, 0, // 64bit connection ID
+ 0x01, // sequence number
+ 0x00, // private flags
+ 0x07, // type: regular frame type "PING"
+ };
+
+ arc4random_buf(quic_packet + 1, 8); // random connection ID
+
+ uint8_t response[1500];
+ ssize_t sent, rcvd;
+ static const int MAX_RETRIES = 5;
+ int i, errnum = 0;
+
+ for (i = 0; i < MAX_RETRIES; i++) {
+ sent = send(fd, quic_packet, sizeof(quic_packet), 0);
+ if (sent < (ssize_t)sizeof(quic_packet)) {
+ errnum = errno;
+ ALOGD("send(QUIC packet) returned sent=%zd, errno=%d", sent, errnum);
+ close(fd);
+ return -errnum;
+ }
+
+ rcvd = recv(fd, response, sizeof(response), 0);
+ if (rcvd > 0) {
+ break;
+ } else {
+ errnum = errno;
+ ALOGD("[%d/%d] recv(QUIC response) returned rcvd=%zd, errno=%d",
+ i + 1, MAX_RETRIES, rcvd, errnum);
+ }
+ }
+ if (rcvd < sent) {
+ ALOGD("QUIC UDP %s: sent=%zd but rcvd=%zd, errno=%d", kPort, sent, rcvd, errnum);
+ if (rcvd <= 0) {
+ ALOGD("Does this network block UDP port %s?", kPort);
+ }
+ close(fd);
+ return -EPROTO;
+ }
+
+ int conn_id_cmp = memcmp(quic_packet + 1, response + 1, 8);
+ if (conn_id_cmp != 0) {
+ ALOGD("sent and received connection IDs do not match");
+ close(fd);
+ return -EPROTO;
+ }
+
+ // TODO: log, and compare to the IP address encoded in the
+ // response, since this should be a public reset packet.
+
+ close(fd);
+ return 0;
+}
diff --git a/tests/cts/net/native/dns/Android.bp b/tests/cts/net/native/dns/Android.bp
new file mode 100644
index 0000000..9fbc3fc
--- /dev/null
+++ b/tests/cts/net/native/dns/Android.bp
@@ -0,0 +1,39 @@
+cc_defaults {
+ name: "dns_async_defaults",
+
+ cflags: [
+ "-fstack-protector-all",
+ "-g",
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ "-Wnullable-to-nonnull-conversion",
+ "-Wsign-compare",
+ "-Wthread-safety",
+ "-Wunused-parameter",
+ ],
+ srcs: [
+ "NativeDnsAsyncTest.cpp",
+ ],
+ shared_libs: [
+ "libandroid",
+ "liblog",
+ "libutils",
+ ],
+}
+
+cc_test {
+ name: "CtsNativeNetDnsTestCases",
+ defaults: ["dns_async_defaults"],
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+ test_suites: [
+ "cts",
+ ],
+}
\ No newline at end of file
diff --git a/tests/cts/net/native/dns/AndroidTest.xml b/tests/cts/net/native/dns/AndroidTest.xml
new file mode 100644
index 0000000..fe88cda
--- /dev/null
+++ b/tests/cts/net/native/dns/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for CTS Native Network dns test cases">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="networking" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsNativeNetDnsTestCases->/data/local/tmp/CtsNativeNetDnsTestCases" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="CtsNativeNetDnsTestCases" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+</configuration>
diff --git a/tests/cts/net/native/dns/NativeDnsAsyncTest.cpp b/tests/cts/net/native/dns/NativeDnsAsyncTest.cpp
new file mode 100644
index 0000000..e501475
--- /dev/null
+++ b/tests/cts/net/native/dns/NativeDnsAsyncTest.cpp
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <error.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <poll.h> /* poll */
+#include <resolv.h>
+#include <string.h>
+#include <sys/socket.h>
+
+#include <android/multinetwork.h>
+#include <gtest/gtest.h>
+
+namespace {
+constexpr int MAXPACKET = 8 * 1024;
+constexpr int PTON_MAX = 16;
+constexpr int TIMEOUT_MS = 10000;
+
+int getAsyncResponse(int fd, int timeoutMs, int* rcode, uint8_t* buf, size_t bufLen) {
+ struct pollfd wait_fd[1];
+ wait_fd[0].fd = fd;
+ wait_fd[0].events = POLLIN;
+ short revents;
+ int ret;
+ ret = poll(wait_fd, 1, timeoutMs);
+ revents = wait_fd[0].revents;
+ if (revents & POLLIN) {
+ int n = android_res_nresult(fd, rcode, buf, bufLen);
+ // Verify that android_res_nresult() closed the fd
+ char dummy;
+ EXPECT_EQ(-1, read(fd, &dummy, sizeof dummy));
+ EXPECT_EQ(EBADF, errno);
+ return n;
+ }
+
+ return -1;
+}
+
+std::vector<std::string> extractIpAddressAnswers(uint8_t* buf, size_t bufLen, int ipType) {
+ ns_msg handle;
+ if (ns_initparse((const uint8_t*) buf, bufLen, &handle) < 0) {
+ return {};
+ }
+ const int ancount = ns_msg_count(handle, ns_s_an);
+ ns_rr rr;
+ std::vector<std::string> answers;
+ for (int i = 0; i < ancount; i++) {
+ if (ns_parserr(&handle, ns_s_an, i, &rr) < 0) {
+ continue;
+ }
+ const uint8_t* rdata = ns_rr_rdata(rr);
+ char buffer[INET6_ADDRSTRLEN];
+ if (inet_ntop(ipType, (const char*) rdata, buffer, sizeof(buffer))) {
+ answers.push_back(buffer);
+ }
+ }
+ return answers;
+}
+
+void expectAnswersValid(int fd, int ipType, int expectedRcode) {
+ int rcode = -1;
+ uint8_t buf[MAXPACKET] = {};
+ int res = getAsyncResponse(fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
+ EXPECT_GE(res, 0);
+ EXPECT_EQ(rcode, expectedRcode);
+
+ if (expectedRcode == ns_r_noerror) {
+ auto answers = extractIpAddressAnswers(buf, res, ipType);
+ EXPECT_GE(answers.size(), 0U);
+ for (auto &answer : answers) {
+ char pton[PTON_MAX];
+ EXPECT_EQ(1, inet_pton(ipType, answer.c_str(), pton));
+ }
+ }
+}
+
+void expectAnswersNotValid(int fd, int expectedErrno) {
+ int rcode = -1;
+ uint8_t buf[MAXPACKET] = {};
+ int res = getAsyncResponse(fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
+ EXPECT_EQ(expectedErrno, res);
+}
+
+} // namespace
+
+TEST (NativeDnsAsyncTest, Async_Query) {
+ // V4
+ int fd1 = android_res_nquery(
+ NETWORK_UNSPECIFIED, "www.google.com", ns_c_in, ns_t_a, 0);
+ EXPECT_GE(fd1, 0);
+ int fd2 = android_res_nquery(
+ NETWORK_UNSPECIFIED, "www.youtube.com", ns_c_in, ns_t_a, 0);
+ EXPECT_GE(fd2, 0);
+ expectAnswersValid(fd2, AF_INET, ns_r_noerror);
+ expectAnswersValid(fd1, AF_INET, ns_r_noerror);
+
+ // V6
+ fd1 = android_res_nquery(
+ NETWORK_UNSPECIFIED, "www.google.com", ns_c_in, ns_t_aaaa, 0);
+ EXPECT_GE(fd1, 0);
+ fd2 = android_res_nquery(
+ NETWORK_UNSPECIFIED, "www.youtube.com", ns_c_in, ns_t_aaaa, 0);
+ EXPECT_GE(fd2, 0);
+ expectAnswersValid(fd2, AF_INET6, ns_r_noerror);
+ expectAnswersValid(fd1, AF_INET6, ns_r_noerror);
+}
+
+TEST (NativeDnsAsyncTest, Async_Send) {
+ // V4
+ uint8_t buf1[MAXPACKET] = {};
+ int len1 = res_mkquery(ns_o_query, "www.googleapis.com",
+ ns_c_in, ns_t_a, nullptr, 0, nullptr, buf1, sizeof(buf1));
+ EXPECT_GT(len1, 0);
+
+ uint8_t buf2[MAXPACKET] = {};
+ int len2 = res_mkquery(ns_o_query, "play.googleapis.com",
+ ns_c_in, ns_t_a, nullptr, 0, nullptr, buf2, sizeof(buf2));
+ EXPECT_GT(len2, 0);
+
+ int fd1 = android_res_nsend(NETWORK_UNSPECIFIED, buf1, len1, 0);
+ EXPECT_GE(fd1, 0);
+ int fd2 = android_res_nsend(NETWORK_UNSPECIFIED, buf2, len2, 0);
+ EXPECT_GE(fd2, 0);
+
+ expectAnswersValid(fd2, AF_INET, ns_r_noerror);
+ expectAnswersValid(fd1, AF_INET, ns_r_noerror);
+
+ // V6
+ memset(buf1, 0, sizeof(buf1));
+ memset(buf2, 0, sizeof(buf2));
+ len1 = res_mkquery(ns_o_query, "www.googleapis.com",
+ ns_c_in, ns_t_aaaa, nullptr, 0, nullptr, buf1, sizeof(buf1));
+ EXPECT_GT(len1, 0);
+ len2 = res_mkquery(ns_o_query, "play.googleapis.com",
+ ns_c_in, ns_t_aaaa, nullptr, 0, nullptr, buf2, sizeof(buf2));
+ EXPECT_GT(len2, 0);
+
+ fd1 = android_res_nsend(NETWORK_UNSPECIFIED, buf1, len1, 0);
+ EXPECT_GE(fd1, 0);
+ fd2 = android_res_nsend(NETWORK_UNSPECIFIED, buf2, len2, 0);
+ EXPECT_GE(fd2, 0);
+
+ expectAnswersValid(fd2, AF_INET6, ns_r_noerror);
+ expectAnswersValid(fd1, AF_INET6, ns_r_noerror);
+}
+
+TEST (NativeDnsAsyncTest, Async_NXDOMAIN) {
+ uint8_t buf[MAXPACKET] = {};
+ int len = res_mkquery(ns_o_query, "test1-nx.metric.gstatic.com",
+ ns_c_in, ns_t_a, nullptr, 0, nullptr, buf, sizeof(buf));
+ EXPECT_GT(len, 0);
+ int fd1 = android_res_nsend(NETWORK_UNSPECIFIED, buf, len, ANDROID_RESOLV_NO_CACHE_LOOKUP);
+ EXPECT_GE(fd1, 0);
+
+ len = res_mkquery(ns_o_query, "test2-nx.metric.gstatic.com",
+ ns_c_in, ns_t_a, nullptr, 0, nullptr, buf, sizeof(buf));
+ EXPECT_GT(len, 0);
+ int fd2 = android_res_nsend(NETWORK_UNSPECIFIED, buf, len, ANDROID_RESOLV_NO_CACHE_LOOKUP);
+ EXPECT_GE(fd2, 0);
+
+ expectAnswersValid(fd2, AF_INET, ns_r_nxdomain);
+ expectAnswersValid(fd1, AF_INET, ns_r_nxdomain);
+
+ fd1 = android_res_nquery(
+ NETWORK_UNSPECIFIED, "test3-nx.metric.gstatic.com",
+ ns_c_in, ns_t_aaaa, ANDROID_RESOLV_NO_CACHE_LOOKUP);
+ EXPECT_GE(fd1, 0);
+ fd2 = android_res_nquery(
+ NETWORK_UNSPECIFIED, "test4-nx.metric.gstatic.com",
+ ns_c_in, ns_t_aaaa, ANDROID_RESOLV_NO_CACHE_LOOKUP);
+ EXPECT_GE(fd2, 0);
+ expectAnswersValid(fd2, AF_INET6, ns_r_nxdomain);
+ expectAnswersValid(fd1, AF_INET6, ns_r_nxdomain);
+}
+
+TEST (NativeDnsAsyncTest, Async_Cancel) {
+ int fd = android_res_nquery(
+ NETWORK_UNSPECIFIED, "www.google.com", ns_c_in, ns_t_a, 0);
+ errno = 0;
+ android_res_cancel(fd);
+ int err = errno;
+ EXPECT_EQ(err, 0);
+ // DO NOT call cancel or result with the same fd more than once,
+ // otherwise it will hit fdsan double-close fd.
+}
+
+TEST (NativeDnsAsyncTest, Async_Query_MALFORMED) {
+ // Empty string to create BLOB and query, we will get empty result and rcode = 0
+ // on DNSTLS.
+ int fd = android_res_nquery(
+ NETWORK_UNSPECIFIED, "", ns_c_in, ns_t_a, 0);
+ EXPECT_GE(fd, 0);
+ expectAnswersValid(fd, AF_INET, ns_r_noerror);
+
+ std::string exceedingLabelQuery = "www." + std::string(70, 'g') + ".com";
+ std::string exceedingDomainQuery = "www." + std::string(255, 'g') + ".com";
+
+ fd = android_res_nquery(NETWORK_UNSPECIFIED,
+ exceedingLabelQuery.c_str(), ns_c_in, ns_t_a, 0);
+ EXPECT_EQ(-EMSGSIZE, fd);
+ fd = android_res_nquery(NETWORK_UNSPECIFIED,
+ exceedingDomainQuery.c_str(), ns_c_in, ns_t_a, 0);
+ EXPECT_EQ(-EMSGSIZE, fd);
+}
+
+TEST (NativeDnsAsyncTest, Async_Send_MALFORMED) {
+ uint8_t buf[10] = {};
+ // empty BLOB
+ int fd = android_res_nsend(NETWORK_UNSPECIFIED, buf, 10, 0);
+ EXPECT_GE(fd, 0);
+ expectAnswersNotValid(fd, -EINVAL);
+
+ std::vector<uint8_t> largeBuf(2 * MAXPACKET, 0);
+ // A buffer larger than 8KB
+ fd = android_res_nsend(
+ NETWORK_UNSPECIFIED, largeBuf.data(), largeBuf.size(), 0);
+ EXPECT_EQ(-EMSGSIZE, fd);
+
+ // 5000 bytes filled with 0. This returns EMSGSIZE because FrameworkListener limits the size of
+ // commands to 4096 bytes.
+ fd = android_res_nsend(NETWORK_UNSPECIFIED, largeBuf.data(), 5000, 0);
+ EXPECT_EQ(-EMSGSIZE, fd);
+
+ // 500 bytes filled with 0
+ fd = android_res_nsend(NETWORK_UNSPECIFIED, largeBuf.data(), 500, 0);
+ EXPECT_GE(fd, 0);
+ expectAnswersNotValid(fd, -EINVAL);
+
+ // 5000 bytes filled with 0xFF
+ std::vector<uint8_t> ffBuf(5000, 0xFF);
+ fd = android_res_nsend(
+ NETWORK_UNSPECIFIED, ffBuf.data(), ffBuf.size(), 0);
+ EXPECT_EQ(-EMSGSIZE, fd);
+
+ // 500 bytes filled with 0xFF
+ fd = android_res_nsend(NETWORK_UNSPECIFIED, ffBuf.data(), 500, 0);
+ EXPECT_GE(fd, 0);
+ expectAnswersNotValid(fd, -EINVAL);
+}
diff --git a/tests/cts/net/native/qtaguid/AndroidTest.xml b/tests/cts/net/native/qtaguid/AndroidTest.xml
index 7591c87..a55afe7 100644
--- a/tests/cts/net/native/qtaguid/AndroidTest.xml
+++ b/tests/cts/net/native/qtaguid/AndroidTest.xml
@@ -16,6 +16,8 @@
<configuration description="Config for CTS Native Network xt_qtaguid test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="networking" />
+ <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
<option name="push" value="CtsNativeNetTestCases->/data/local/tmp/CtsNativeNetTestCases" />
diff --git a/tests/cts/net/native/qtaguid/src/NativeQtaguidTest.cpp b/tests/cts/net/native/qtaguid/src/NativeQtaguidTest.cpp
index 1892a44..7dc6240 100644
--- a/tests/cts/net/native/qtaguid/src/NativeQtaguidTest.cpp
+++ b/tests/cts/net/native/qtaguid/src/NativeQtaguidTest.cpp
@@ -18,36 +18,29 @@
#include <error.h>
#include <errno.h>
#include <inttypes.h>
+#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
-#include <sys/utsname.h>
#include <gtest/gtest.h>
#include <qtaguid/qtaguid.h>
-int hasQtaguidKernelSupport() {
- struct utsname buf;
- int kernel_version_major;
- int kernel_version_minor;
-
- int ret = uname(&buf);
- if (ret) {
- ret = -errno;
- return ret;
- }
- char dummy;
- ret = sscanf(buf.release, "%d.%d%c", &kernel_version_major, &kernel_version_minor, &dummy);
- if (ret < 3)
- return -EINVAL;
-
- if ((kernel_version_major == 4 && kernel_version_minor < 9) ||
- (kernel_version_major < 4)) {
- return 1;
- } else {
- return access("/proc/net/xt_qtaguid/ctrl", F_OK) != -1;
- }
+int canAccessQtaguidFile() {
+ int fd = open("/proc/net/xt_qtaguid/ctrl", O_RDONLY | O_CLOEXEC);
+ close(fd);
+ return fd != -1;
}
+#define SKIP_IF_QTAGUID_NOT_SUPPORTED() \
+ do { \
+ int res = canAccessQtaguidFile(); \
+ ASSERT_LE(0, res); \
+ if (!res) { \
+ GTEST_LOG_(INFO) << "This test is skipped since kernel may not have the module\n"; \
+ return; \
+ } \
+ } while (0)
+
int getCtrlSkInfo(int tag, uid_t uid, uint64_t* sk_addr, int* ref_cnt) {
FILE *fp;
fp = fopen("/proc/net/xt_qtaguid/ctrl", "r");
@@ -95,12 +88,8 @@
}
TEST (NativeQtaguidTest, close_socket_without_untag) {
- int res = hasQtaguidKernelSupport();
- ASSERT_LE(0, res);
- if (!res) {
- GTEST_LOG_(INFO) << "This test is skipped since kernel may not have the module\n";
- return;
- }
+ SKIP_IF_QTAGUID_NOT_SUPPORTED();
+
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
uid_t uid = getuid();
int tag = arc4random();
@@ -114,12 +103,8 @@
}
TEST (NativeQtaguidTest, close_socket_without_untag_ipv6) {
- int res = hasQtaguidKernelSupport();
- ASSERT_LE(0, res);
- if (!res) {
- GTEST_LOG_(INFO) << "This test is skipped since kernel may not have the module\n";
- return;
- }
+ SKIP_IF_QTAGUID_NOT_SUPPORTED();
+
int sockfd = socket(AF_INET6, SOCK_STREAM, 0);
uid_t uid = getuid();
int tag = arc4random();
@@ -133,12 +118,8 @@
}
TEST (NativeQtaguidTest, no_socket_addr_leak) {
- int res = hasQtaguidKernelSupport();
- ASSERT_LE(0, res);
- if (!res) {
- GTEST_LOG_(INFO) << "This test is skipped since kernel may not have the module\n";
- return;
- }
+ SKIP_IF_QTAGUID_NOT_SUPPORTED();
+
checkNoSocketPointerLeaks(AF_INET);
checkNoSocketPointerLeaks(AF_INET6);
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 810b5df..4180ea4 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -23,12 +23,17 @@
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.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.AF_UNSPEC;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import android.app.Instrumentation;
import android.app.PendingIntent;
+import android.app.UiAutomation;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -46,8 +51,10 @@
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
import android.net.NetworkRequest;
+import android.net.SocketKeepalive;
import android.net.wifi.WifiManager;
import android.os.Looper;
+import android.os.MessageQueue;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Settings;
@@ -64,13 +71,13 @@
import libcore.io.Streams;
-import java.io.File;
-import java.io.FileNotFoundException;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
+import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -80,8 +87,8 @@
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashMap;
-import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
@@ -99,7 +106,11 @@
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;
private static final int SEND_BROADCAST_TIMEOUT = 30000;
+ private static final int MIN_KEEPALIVE_INTERVAL = 10;
private static final int NETWORK_CHANGE_METEREDNESS_TIMEOUT = 5000;
private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20;
private static final long INTERVAL_MULTIPATH_PREF_CHECK_MS = 500;
@@ -109,17 +120,6 @@
"Host: " + TEST_HOST + "\r\n" +
"Connection: keep-alive\r\n\r\n";
- // Base path for IPv6 sysctls
- private static final String IPV6_SYSCTL_DIR = "/proc/sys/net/ipv6/conf";
-
- // Expected values for MIN|MAX_PLEN.
- private static final int IPV6_WIFI_ACCEPT_RA_RT_INFO_MIN_PLEN = 48;
- private static final int IPV6_WIFI_ACCEPT_RA_RT_INFO_MAX_PLEN = 64;
-
- // Expected values for RFC 7559 router soliciations.
- // Maximum number of router solicitations to send. -1 means no limit.
- private static final int IPV6_WIFI_ROUTER_SOLICITATIONS = -1;
-
// Action sent to ConnectivityActionReceiver when a network callback is sent via PendingIntent.
private static final String NETWORK_CALLBACK_ACTION =
"ConnectivityManagerTest.NetworkCallbackAction";
@@ -140,7 +140,8 @@
new HashMap<Integer, NetworkConfig>();
boolean mWifiConnectAttempted;
private TestNetworkCallback mCellNetworkCallback;
-
+ private UiAutomation mUiAutomation;
+ private boolean mShellPermissionIdentityAdopted;
@Override
protected void setUp() throws Exception {
@@ -167,6 +168,8 @@
mNetworks.put(n.type, n);
} catch (Exception e) {}
}
+ mUiAutomation = mInstrumentation.getUiAutomation();
+ mShellPermissionIdentityAdopted = false;
}
@Override
@@ -178,6 +181,7 @@
if (cellConnectAttempted()) {
disconnectFromCell();
}
+ dropShellPermissionIdentity();
super.tearDown();
}
@@ -910,60 +914,6 @@
} catch (SecurityException expected) {}
}
- private Scanner makeWifiSysctlScanner(String key) throws FileNotFoundException {
- Network network = ensureWifiConnected();
- String iface = mCm.getLinkProperties(network).getInterfaceName();
- String path = IPV6_SYSCTL_DIR + "/" + iface + "/" + key;
- return new Scanner(new File(path));
- }
-
- /** Verify that accept_ra_rt_info_min_plen exists and is set to the expected value */
- public void testAcceptRaRtInfoMinPlen() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
- return;
- }
- Scanner s = makeWifiSysctlScanner("accept_ra_rt_info_min_plen");
- assertEquals(IPV6_WIFI_ACCEPT_RA_RT_INFO_MIN_PLEN, s.nextInt());
- }
-
- /** Verify that accept_ra_rt_info_max_plen exists and is set to the expected value */
- public void testAcceptRaRtInfoMaxPlen() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
- return;
- }
- Scanner s = makeWifiSysctlScanner("accept_ra_rt_info_max_plen");
- assertEquals(IPV6_WIFI_ACCEPT_RA_RT_INFO_MAX_PLEN, s.nextInt());
- }
-
- /** Verify that router_solicitations exists and is set to the expected value */
- public void testRouterSolicitations() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
- return;
- }
- Scanner s = makeWifiSysctlScanner("router_solicitations");
- assertEquals(IPV6_WIFI_ROUTER_SOLICITATIONS, s.nextInt());
- }
-
- /** Verify that router_solicitation_max_interval exists and is in an acceptable interval */
- public void testRouterSolicitationMaxInterval() throws Exception {
- if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)) {
- Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot execute unless device supports WiFi");
- return;
- }
- Scanner s = makeWifiSysctlScanner("router_solicitation_max_interval");
- int interval = s.nextInt();
- // Verify we're in the interval [15 minutes, 60 minutes]. Lower values may adversely
- // impact battery life and higher values can decrease the probability of detecting
- // network changes.
- final int lowerBoundSec = 15 * 60;
- final int upperBoundSec = 60 * 60;
- assertTrue(lowerBoundSec <= interval);
- assertTrue(interval <= upperBoundSec);
- }
-
// Returns "true", "false" or "none"
private String getWifiMeteredStatus(String ssid) throws Exception {
// Interestingly giving the SSID as an argument to list wifi-networks
@@ -1105,4 +1055,226 @@
setWifiMeteredStatus(ssid, oldMeteredSetting);
}
}
+
+ // TODO: move the following socket keep alive test to dedicated test class.
+ /**
+ * Callback used in tcp keepalive offload that allows caller to wait callback fires.
+ */
+ private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback {
+ public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
+
+ public static class CallbackValue {
+ public final CallbackType callbackType;
+ public final int error;
+
+ private CallbackValue(final CallbackType type, final int error) {
+ this.callbackType = type;
+ this.error = error;
+ }
+
+ public static class OnStartedCallback extends CallbackValue {
+ OnStartedCallback() { super(CallbackType.ON_STARTED, 0); }
+ }
+
+ public static class OnStoppedCallback extends CallbackValue {
+ OnStoppedCallback() { super(CallbackType.ON_STOPPED, 0); }
+ }
+
+ public static class OnErrorCallback extends CallbackValue {
+ OnErrorCallback(final int error) { super(CallbackType.ON_ERROR, error); }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o.getClass() == this.getClass()
+ && this.callbackType == ((CallbackValue) o).callbackType
+ && this.error == ((CallbackValue) o).error;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, error);
+ }
+ }
+
+ private final LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
+
+ @Override
+ public void onStarted() {
+ mCallbacks.add(new CallbackValue.OnStartedCallback());
+ }
+
+ @Override
+ public void onStopped() {
+ mCallbacks.add(new CallbackValue.OnStoppedCallback());
+ }
+
+ @Override
+ public void onError(final int error) {
+ mCallbacks.add(new CallbackValue.OnErrorCallback(error));
+ }
+
+ public CallbackValue pollCallback() {
+ try {
+ return mCallbacks.poll(KEEPALIVE_CALLBACK_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail("Callback not seen after " + KEEPALIVE_CALLBACK_TIMEOUT_MS + " ms");
+ }
+ return null;
+ }
+ private void expectCallback(CallbackValue expectedCallback) {
+ final CallbackValue actualCallback = pollCallback();
+ assertEquals(expectedCallback, actualCallback);
+ }
+
+ public void expectStarted() {
+ expectCallback(new CallbackValue.OnStartedCallback());
+ }
+
+ public void expectStopped() {
+ expectCallback(new CallbackValue.OnStoppedCallback());
+ }
+
+ public void expectError(int error) {
+ expectCallback(new CallbackValue.OnErrorCallback(error));
+ }
+ }
+
+ private InetAddress getAddrByName(final String hostname, final int family) throws Exception {
+ final InetAddress[] allAddrs = InetAddress.getAllByName(hostname);
+ for (InetAddress addr : allAddrs) {
+ if (family == AF_INET && addr instanceof Inet4Address) return addr;
+
+ if (family == AF_INET6 && addr instanceof Inet6Address) return addr;
+
+ if (family == AF_UNSPEC) return addr;
+ }
+ return null;
+ }
+
+ private Socket getConnectedSocket(final Network network, final String host, final int port,
+ final int socketTimeOut, final int family) throws Exception {
+ final Socket s = network.getSocketFactory().createSocket();
+ try {
+ final InetAddress addr = getAddrByName(host, family);
+ if (addr == null) fail("Fail to get destination address for " + family);
+
+ final InetSocketAddress sockAddr = new InetSocketAddress(addr, port);
+ s.setSoTimeout(socketTimeOut);
+ s.connect(sockAddr, CONNECT_TIMEOUT_MS);
+ } catch (Exception e) {
+ s.close();
+ throw e;
+ }
+ return s;
+ }
+
+ private 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;
+ }
+
+ private void dropShellPermissionIdentity() {
+ if (mShellPermissionIdentityAdopted) {
+ mUiAutomation.dropShellPermissionIdentity();
+ mShellPermissionIdentityAdopted = false;
+ }
+ }
+
+ public void testCreateTcpKeepalive() throws Exception {
+ adoptShellPermissionIdentity();
+
+ if (!isKeepaliveSupported()) return;
+
+ final Network network = ensureWifiConnected();
+ final byte[] requestBytes = HTTP_REQUEST.getBytes("UTF-8");
+ // So far only ipv4 tcp keepalive offload is supported.
+ // TODO: add test case for ipv6 tcp keepalive offload when it is supported.
+ try (Socket s = getConnectedSocket(network, TEST_HOST, HTTP_PORT,
+ KEEPALIVE_SOCKET_TIMEOUT_MS, AF_INET)) {
+
+ // Should able to start keep alive offload when socket is idle.
+ final Executor executor = mContext.getMainExecutor();
+ final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
+ try (SocketKeepalive sk = mCm.createSocketKeepalive(network, s, executor, callback)) {
+ sk.start(MIN_KEEPALIVE_INTERVAL);
+ callback.expectStarted();
+
+ // App should not able to write during keepalive offload.
+ final OutputStream out = s.getOutputStream();
+ try {
+ out.write(requestBytes);
+ fail("Should not able to write");
+ } catch (IOException e) { }
+ // App should not able to read during keepalive offload.
+ final InputStream in = s.getInputStream();
+ byte[] responseBytes = new byte[4096];
+ try {
+ in.read(responseBytes);
+ fail("Should not able to read");
+ } catch (IOException e) { }
+
+ // Stop.
+ sk.stop();
+ callback.expectStopped();
+ }
+
+ // Ensure socket is still connected.
+ assertTrue(s.isConnected());
+ assertFalse(s.isClosed());
+
+ // Let socket be not idle.
+ try {
+ final OutputStream out = s.getOutputStream();
+ out.write(requestBytes);
+ } catch (IOException e) {
+ fail("Failed to write data " + e);
+ }
+ // Make sure response data arrives.
+ final MessageQueue fdHandlerQueue = Looper.getMainLooper().getQueue();
+ final FileDescriptor fd = s.getFileDescriptor$();
+ final CountDownLatch mOnReceiveLatch = new CountDownLatch(1);
+ fdHandlerQueue.addOnFileDescriptorEventListener(fd, EVENT_INPUT, (readyFd, events) -> {
+ mOnReceiveLatch.countDown();
+ return 0; // Unregister listener.
+ });
+ if (!mOnReceiveLatch.await(2, TimeUnit.SECONDS)) {
+ fdHandlerQueue.removeOnFileDescriptorEventListener(fd);
+ fail("Timeout: no response data");
+ }
+
+ // Should get ERROR_SOCKET_NOT_IDLE because there is still data in the receive queue
+ // that has not been read.
+ try (SocketKeepalive sk = mCm.createSocketKeepalive(network, s, executor, callback)) {
+ sk.start(MIN_KEEPALIVE_INTERVAL);
+ callback.expectError(SocketKeepalive.ERROR_SOCKET_NOT_IDLE);
+ }
+
+ }
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
new file mode 100644
index 0000000..0ff6cd8
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -0,0 +1,576 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import static android.net.DnsResolver.CLASS_IN;
+import static android.net.DnsResolver.FLAG_EMPTY;
+import static android.net.DnsResolver.FLAG_NO_CACHE_LOOKUP;
+import static android.net.DnsResolver.TYPE_A;
+import static android.net.DnsResolver.TYPE_AAAA;
+import static android.system.OsConstants.EBADF;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.DnsPacket;
+import android.net.DnsResolver;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.ParseException;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Looper;
+import android.system.ErrnoException;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class DnsResolverTest extends AndroidTestCase {
+ private static final String TAG = "DnsResolverTest";
+ private static final char[] HEX_CHARS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+ };
+ static final int TIMEOUT_MS = 12_000;
+ static final int CANCEL_RETRY_TIMES = 5;
+
+ private ConnectivityManager mCM;
+ private Executor mExecutor;
+ private DnsResolver mDns;
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+ mDns = DnsResolver.getInstance();
+ mExecutor = new Handler(Looper.getMainLooper())::post;
+ }
+
+ private static String byteArrayToHexString(byte[] bytes) {
+ char[] hexChars = new char[bytes.length * 2];
+ for (int i = 0; i < bytes.length; ++i) {
+ int b = bytes[i] & 0xFF;
+ hexChars[i * 2] = HEX_CHARS[b >>> 4];
+ hexChars[i * 2 + 1] = HEX_CHARS[b & 0x0F];
+ }
+ return new String(hexChars);
+ }
+
+ private Network[] getTestableNetworks() {
+ final ArrayList<Network> testableNetworks = new ArrayList<Network>();
+ for (Network network : mCM.getAllNetworks()) {
+ final NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
+ if (nc != null
+ && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+ testableNetworks.add(network);
+ }
+ }
+
+ assertTrue(
+ "This test requires that at least one network be connected. " +
+ "Please ensure that the device is connected to a network.",
+ testableNetworks.size() >= 1);
+ return testableNetworks.toArray(new Network[0]);
+ }
+
+ public void testQueryWithInetAddressCallback() {
+ final String dname = "www.google.com";
+ final String msg = "Query with InetAddressAnswerCallback " + dname;
+ for (Network network : getTestableNetworks()) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicReference<List<InetAddress>> answers = new AtomicReference<>();
+ final DnsResolver.InetAddressAnswerCallback callback =
+ new DnsResolver.InetAddressAnswerCallback() {
+ @Override
+ public void onAnswer(@NonNull List<InetAddress> answerList) {
+ answers.set(answerList);
+ for (InetAddress addr : answerList) {
+ Log.d(TAG, "Reported addr: " + addr.toString());
+ }
+ latch.countDown();
+ }
+
+ @Override
+ public void onParseException(@NonNull ParseException e) {
+ fail(msg + e.getMessage());
+ }
+
+ @Override
+ public void onQueryException(@NonNull ErrnoException e) {
+ fail(msg + e.getMessage());
+ }
+ };
+ mDns.query(network, dname, CLASS_IN, TYPE_A, FLAG_NO_CACHE_LOOKUP,
+ mExecutor, null, callback);
+ try {
+ assertTrue(msg + " but no valid answer after " + TIMEOUT_MS + "ms.",
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertGreaterThan(msg + " returned 0 result", answers.get().size(), 0);
+ } catch (InterruptedException e) {
+ fail(msg + " Waiting for DNS lookup was interrupted");
+ }
+ }
+ }
+
+ static private void assertGreaterThan(String msg, int first, int second) {
+ assertTrue(msg + " Excepted " + first + " to be greater than " + second, first > second);
+ }
+
+ static private void assertValidAnswer(String msg, @NonNull DnsAnswer ans) {
+ // Check rcode field.(0, No error condition).
+ assertTrue(msg + " Response error, rcode: " + ans.getRcode(), ans.getRcode() == 0);
+ // Check answer counts.
+ assertGreaterThan(msg + " No answer found", ans.getANCount(), 0);
+ // Check question counts.
+ assertGreaterThan(msg + " No question found", ans.getQDCount(), 0);
+ }
+
+ static private void assertValidEmptyAnswer(String msg, @NonNull DnsAnswer ans) {
+ // Check rcode field.(0, No error condition).
+ assertTrue(msg + " Response error, rcode: " + ans.getRcode(), ans.getRcode() == 0);
+ // Check answer counts. Expect 0 answer.
+ assertTrue(msg + " Not an empty answer", ans.getANCount() == 0);
+ // Check question counts.
+ assertGreaterThan(msg + " No question found", ans.getQDCount(), 0);
+ }
+
+ private static class DnsAnswer extends DnsPacket {
+ DnsAnswer(@NonNull byte[] data) throws ParseException {
+ super(data);
+ // Check QR field.(query (0), or a response (1)).
+ if ((mHeader.flags & (1 << 15)) == 0) {
+ throw new ParseException("Not an answer packet");
+ }
+ }
+
+ int getRcode() {
+ return mHeader.rcode;
+ }
+ int getANCount(){
+ return mHeader.getRecordCount(ANSECTION);
+ }
+ int getQDCount(){
+ return mHeader.getRecordCount(QDSECTION);
+ }
+ }
+
+ class RawAnswerCallbackImpl extends DnsResolver.RawAnswerCallback {
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private final String mMsg;
+ private final int mTimeout;
+
+ RawAnswerCallbackImpl(@NonNull String msg, int timeout) {
+ this.mMsg = msg;
+ this.mTimeout = timeout;
+ }
+
+ RawAnswerCallbackImpl(@NonNull String msg) {
+ this(msg, TIMEOUT_MS);
+ }
+
+ public boolean waitForAnswer() throws InterruptedException {
+ return mLatch.await(mTimeout, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onAnswer(@NonNull byte[] answer) {
+ try {
+ assertValidAnswer(mMsg, new DnsAnswer(answer));
+ Log.d(TAG, "Reported blob: " + byteArrayToHexString(answer));
+ mLatch.countDown();
+ } catch (ParseException e) {
+ fail(mMsg + e.getMessage());
+ }
+ }
+
+ @Override
+ public void onParseException(@NonNull ParseException e) {
+ fail(mMsg + e.getMessage());
+ }
+
+ @Override
+ public void onQueryException(@NonNull ErrnoException e) {
+ fail(mMsg + e.getMessage());
+ }
+ }
+
+ public void testQueryWithRawAnswerCallback() {
+ final String dname = "www.google.com";
+ final String msg = "Query with RawAnswerCallback " + dname;
+ for (Network network : getTestableNetworks()) {
+ final RawAnswerCallbackImpl callback = new RawAnswerCallbackImpl(msg);
+ mDns.query(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());
+ } catch (InterruptedException e) {
+ fail(msg + " Waiting for DNS lookup was interrupted");
+ }
+ }
+ }
+
+ public void testQueryBlobWithRawAnswerCallback() {
+ 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 = "Query with RawAnswerCallback " + byteArrayToHexString(blob);
+ for (Network network : getTestableNetworks()) {
+ final RawAnswerCallbackImpl callback = new RawAnswerCallbackImpl(msg);
+ mDns.query(network, blob, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callback);
+ try {
+ assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+ callback.waitForAnswer());
+ } catch (InterruptedException e) {
+ fail(msg + " Waiting for DNS lookup was interrupted");
+ }
+ }
+ }
+
+ public void testQueryRoot() {
+ final String dname = "";
+ final String msg = "Query with RawAnswerCallback empty dname(ROOT) ";
+ for (Network network : getTestableNetworks()) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final DnsResolver.RawAnswerCallback callback = new DnsResolver.RawAnswerCallback() {
+ @Override
+ public void onAnswer(@NonNull byte[] answer) {
+ try {
+ // Except no answer record because of querying with empty dname(ROOT)
+ assertValidEmptyAnswer(msg, new DnsAnswer(answer));
+ latch.countDown();
+ } catch (ParseException e) {
+ fail(msg + e.getMessage());
+ }
+ }
+
+ @Override
+ public void onParseException(@NonNull ParseException e) {
+ fail(msg + e.getMessage());
+ }
+
+ @Override
+ public void onQueryException(@NonNull ErrnoException e) {
+ fail(msg + e.getMessage());
+ }
+ };
+ mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
+ mExecutor, null, callback);
+ try {
+ assertTrue(msg + "but no answer after " + TIMEOUT_MS + "ms.",
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(msg + "Waiting for DNS lookup was interrupted");
+ }
+ }
+ }
+
+ public void testQueryNXDomain() {
+ final String dname = "test1-nx.metric.gstatic.com";
+ final String msg = "Query with InetAddressAnswerCallback " + dname;
+ for (Network network : getTestableNetworks()) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final DnsResolver.InetAddressAnswerCallback callback =
+ new DnsResolver.InetAddressAnswerCallback() {
+ @Override
+ public void onAnswer(@NonNull List<InetAddress> answerList) {
+ if (answerList.size() == 0) {
+ latch.countDown();
+ return;
+ }
+ fail(msg + " but get valid answers");
+ }
+
+ @Override
+ public void onParseException(@NonNull ParseException e) {
+ fail(msg + e.getMessage());
+ }
+
+ @Override
+ public void onQueryException(@NonNull ErrnoException e) {
+ fail(msg + e.getMessage());
+ }
+ };
+ mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
+ mExecutor, null, callback);
+ try {
+ assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(msg + " Waiting for DNS lookup was interrupted");
+ }
+ }
+ }
+
+ /**
+ * A query callback that ensures that the query is cancelled and that onAnswer is never
+ * called. If the query succeeds before it is cancelled, needRetry will return true so the
+ * test can retry.
+ */
+ class VerifyCancelCallback extends DnsResolver.RawAnswerCallback {
+ private static final int CANCEL_TIMEOUT = 3_000;
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private final String mMsg;
+ private final CancellationSignal mCancelSignal;
+
+ VerifyCancelCallback(@NonNull String msg, @NonNull CancellationSignal cancelSignal) {
+ this.mMsg = msg;
+ this.mCancelSignal = cancelSignal;
+ }
+
+ public boolean needRetry() throws InterruptedException {
+ return mLatch.await(CANCEL_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void onAnswer(@NonNull byte[] answer) {
+ if (mCancelSignal.isCanceled()) {
+ fail(mMsg + " should not have returned any answers");
+ }
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onParseException(@NonNull ParseException e) {
+ fail(mMsg + e.getMessage());
+ }
+
+ @Override
+ public void onQueryException(@NonNull ErrnoException e) {
+ fail(mMsg + e.getMessage());
+ }
+ }
+
+ public void testQueryCancel() throws ErrnoException {
+ final String dname = "www.google.com";
+ final String msg = "Test cancel query " + dname;
+ // Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
+ // that the query is cancelled before it succeeds. If it is not cancelled before it
+ // succeeds, retry the test until it is.
+ for (Network network : getTestableNetworks()) {
+ boolean retry = false;
+ int round = 0;
+ do {
+ if (++round > CANCEL_RETRY_TIMES) {
+ fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
+ }
+ final CountDownLatch latch = new CountDownLatch(1);
+ final CancellationSignal cancelSignal = new CancellationSignal();
+ final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
+ mDns.query(network, dname, 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");
+ }
+ } while (retry);
+ }
+ }
+
+ public void testQueryBlobCancel() 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 raw Query " + byteArrayToHexString(blob);
+ // Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
+ // that the query is cancelled before it succeeds. If it is not cancelled before it
+ // succeeds, retry the test until it is.
+ for (Network network : getTestableNetworks()) {
+ boolean retry = false;
+ int round = 0;
+ do {
+ if (++round > CANCEL_RETRY_TIMES) {
+ fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
+ }
+ final CountDownLatch latch = new CountDownLatch(1);
+ final CancellationSignal cancelSignal = new CancellationSignal();
+ final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
+ mDns.query(network, 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");
+ }
+ } while (retry);
+ }
+ }
+
+ public void testCancelBeforeQuery() throws ErrnoException {
+ final String dname = "www.google.com";
+ final String msg = "Test cancelled query " + dname;
+ for (Network network : getTestableNetworks()) {
+ final RawAnswerCallbackImpl callback = new RawAnswerCallbackImpl(msg, 3_000);
+ final CancellationSignal cancelSignal = new CancellationSignal();
+ cancelSignal.cancel();
+ mDns.query(network, dname, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
+ mExecutor, cancelSignal, callback);
+ try {
+ assertTrue(msg + " should not return any answers",
+ !callback.waitForAnswer());
+ } catch (InterruptedException e) {
+ fail(msg + " Waiting for DNS lookup was interrupted");
+ }
+ }
+ }
+
+ /**
+ * A query callback for InetAddress that ensures that the query is
+ * cancelled and that onAnswer is never called. If the query succeeds
+ * before it is cancelled, needRetry will return true so the
+ * test can retry.
+ */
+ class VerifyCancelInetAddressCallback extends DnsResolver.InetAddressAnswerCallback {
+ private static final int CANCEL_TIMEOUT = 3_000;
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private final String mMsg;
+ private final List<InetAddress> mAnswers;
+ private final CancellationSignal mCancelSignal;
+
+ VerifyCancelInetAddressCallback(@NonNull String msg, @Nullable CancellationSignal cancel) {
+ this.mMsg = msg;
+ this.mCancelSignal = cancel;
+ mAnswers = new ArrayList<>();
+ }
+
+ public boolean waitForAnswer() throws InterruptedException {
+ return mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+
+ public boolean needRetry() throws InterruptedException {
+ return mLatch.await(CANCEL_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+
+ public boolean isAnswerEmpty() {
+ return mAnswers.isEmpty();
+ }
+
+ @Override
+ public void onAnswer(@NonNull List<InetAddress> answerList) {
+ if (mCancelSignal != null && mCancelSignal.isCanceled()) {
+ fail(mMsg + " should not have returned any answers");
+ }
+ mAnswers.clear();
+ mAnswers.addAll(answerList);
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onParseException(@NonNull ParseException e) {
+ fail(mMsg + e.getMessage());
+ }
+
+ @Override
+ public void onQueryException(@NonNull ErrnoException e) {
+ fail(mMsg + e.getMessage());
+ }
+ }
+
+ public void testQueryForInetAddress() {
+ final String dname = "www.google.com";
+ final String msg = "Test query for InetAddress " + dname;
+ 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");
+ }
+ }
+ }
+
+ public void testQueryCancelForInetAddress() throws ErrnoException {
+ final String dname = "www.google.com";
+ final String msg = "Test cancel query for InetAddress " + dname;
+ // Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
+ // that the query is cancelled before it succeeds. If it is not cancelled before it
+ // succeeds, retry the test until it is.
+ for (Network network : getTestableNetworks()) {
+ boolean retry = false;
+ int round = 0;
+ do {
+ if (++round > CANCEL_RETRY_TIMES) {
+ fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
+ }
+ final CountDownLatch latch = new CountDownLatch(1);
+ final CancellationSignal cancelSignal = new CancellationSignal();
+ final VerifyCancelInetAddressCallback callback =
+ new VerifyCancelInetAddressCallback(msg, cancelSignal);
+ mDns.query(network, dname, 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");
+ }
+ } while (retry);
+ }
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/DnsTest.java b/tests/cts/net/src/android/net/cts/DnsTest.java
index 84231c2..746dcb0 100644
--- a/tests/cts/net/src/android/net/cts/DnsTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsTest.java
@@ -287,7 +287,7 @@
final NetworkCallback callback = new NetworkCallback() {
@Override
public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
- if (lp.hasGlobalIPv6Address()) {
+ if (lp.hasGlobalIpv6Address()) {
latch.countDown();
}
}
diff --git a/tests/cts/net/src/android/net/cts/InetAddressesTest.java b/tests/cts/net/src/android/net/cts/InetAddressesTest.java
new file mode 100644
index 0000000..7837ce9
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/InetAddressesTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.cts;
+
+import android.net.InetAddresses;
+import java.net.InetAddress;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(JUnitParamsRunner.class)
+public class InetAddressesTest {
+
+ public static String[][] validNumericAddressesAndStringRepresentation() {
+ return new String[][] {
+ // Regular IPv4.
+ { "1.2.3.4", "1.2.3.4" },
+
+ // Regular IPv6.
+ { "2001:4860:800d::68", "2001:4860:800d::68" },
+ { "1234:5678::9ABC:DEF0", "1234:5678::9abc:def0" },
+ { "2001:cdba:9abc:5678::", "2001:cdba:9abc:5678::" },
+ { "::2001:cdba:9abc:5678", "::2001:cdba:9abc:5678" },
+ { "64:ff9b::1.2.3.4", "64:ff9b::102:304" },
+
+ { "::9abc:5678", "::154.188.86.120" },
+
+ // Mapped IPv4
+ { "::ffff:127.0.0.1", "127.0.0.1" },
+
+ // Android does not recognize Octal (leading 0) cases: they are treated as decimal.
+ { "0177.00.00.01", "177.0.0.1" },
+
+ // Verify that examples from JavaDoc work correctly.
+ { "192.0.2.1", "192.0.2.1" },
+ { "2001:db8::1:2", "2001:db8::1:2" },
+ };
+ }
+
+ public static String[] invalidNumericAddresses() {
+ return new String[] {
+ "",
+ " ",
+ "\t",
+ "\n",
+ "1.2.3.4.",
+ "1.2.3",
+ "1.2",
+ "1",
+ "1234",
+ "0",
+ "0x1.0x2.0x3.0x4",
+ "0x7f.0x00.0x00.0x01",
+ "0256.00.00.01",
+ "fred",
+ "www.google.com",
+ // IPv6 encoded for use in URL as defined in RFC 2732
+ "[fe80::6:2222]",
+ };
+ }
+
+ @Parameters(method = "validNumericAddressesAndStringRepresentation")
+ @Test
+ public void parseNumericAddress(String address, String expectedString) {
+ InetAddress inetAddress = InetAddresses.parseNumericAddress(address);
+ assertEquals(expectedString, inetAddress.getHostAddress());
+ }
+
+ @Parameters(method = "invalidNumericAddresses")
+ @Test
+ public void test_parseNonNumericAddress(String address) {
+ try {
+ InetAddress inetAddress = InetAddresses.parseNumericAddress(address);
+ fail(String.format(
+ "Address %s is not numeric but was parsed as %s", address, inetAddress));
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage()).contains(address);
+ }
+ }
+
+ @Test
+ public void test_parseNumericAddress_null() {
+ try {
+ InetAddress inetAddress = InetAddresses.parseNumericAddress(null);
+ fail(String.format("null is not numeric but was parsed as %s", inetAddress));
+ } catch (NullPointerException e) {
+ // expected
+ }
+ }
+
+ @Parameters(method = "validNumericAddressesAndStringRepresentation")
+ @Test
+ public void test_isNumericAddress(String address, String unused) {
+ assertTrue("expected '" + address + "' to be treated as numeric",
+ InetAddresses.isNumericAddress(address));
+ }
+
+ @Parameters(method = "invalidNumericAddresses")
+ @Test
+ public void test_isNotNumericAddress(String address) {
+ assertFalse("expected '" + address + "' to be treated as non-numeric",
+ InetAddresses.isNumericAddress(address));
+ }
+
+ @Test
+ public void test_isNumericAddress_null() {
+ try {
+ InetAddresses.isNumericAddress(null);
+ fail("expected null to throw a NullPointerException");
+ } catch (NullPointerException e) {
+ // expected
+ }
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
index 7132ecf..35d0f48 100644
--- a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertArrayEquals;
import android.content.Context;
+import android.net.ConnectivityManager;
import android.net.IpSecAlgorithm;
import android.net.IpSecManager;
import android.net.IpSecTransform;
@@ -66,11 +67,13 @@
protected static final byte[] AUTH_KEY = getKey(256);
protected static final byte[] CRYPT_KEY = getKey(256);
+ protected ConnectivityManager mCM;
protected IpSecManager mISM;
protected void setUp() throws Exception {
super.setUp();
mISM = (IpSecManager) getContext().getSystemService(Context.IPSEC_SERVICE);
+ mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
}
protected static byte[] getKey(int bitLength) {
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index a18b2f0..3387064 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -21,8 +21,6 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
-import android.content.Context;
-import android.net.ConnectivityManager;
import android.net.IpSecAlgorithm;
import android.net.IpSecManager;
import android.net.IpSecTransform;
@@ -37,25 +35,15 @@
import java.net.DatagramSocket;
import java.net.Inet6Address;
import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.Arrays;
public class IpSecManagerTest extends IpSecBaseTest {
private static final String TAG = IpSecManagerTest.class.getSimpleName();
- private ConnectivityManager mCM;
-
- private static InetAddress IpAddress(String addrString) {
- try {
- return InetAddress.getByName(addrString);
- } catch (UnknownHostException e) {
- throw new IllegalArgumentException("Invalid IP address: " + e);
- }
- }
-
- private static final InetAddress GOOGLE_DNS_4 = IpAddress("8.8.8.8");
- private static final InetAddress GOOGLE_DNS_6 = IpAddress("2001:4860:4860::8888");
+ private static final InetAddress GOOGLE_DNS_4 = InetAddress.parseNumericAddress("8.8.8.8");
+ private static final InetAddress GOOGLE_DNS_6 =
+ InetAddress.parseNumericAddress("2001:4860:4860::8888");
private static final InetAddress[] GOOGLE_DNS_LIST =
new InetAddress[] {GOOGLE_DNS_4, GOOGLE_DNS_6};
@@ -78,7 +66,6 @@
protected void setUp() throws Exception {
super.setUp();
- mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
}
/*
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
new file mode 100644
index 0000000..5dc9b63
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.IpSecAlgorithm;
+import android.net.IpSecManager;
+import android.net.IpSecTransform;
+import android.net.Network;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+
+public class IpSecManagerTunnelTest extends IpSecBaseTest {
+
+ private static final String TAG = IpSecManagerTunnelTest.class.getSimpleName();
+ private static final int IP4_PREFIX_LEN = 24;
+ private static final int IP6_PREFIX_LEN = 48;
+ private static final InetAddress OUTER_ADDR4 = InetAddress.parseNumericAddress("192.0.2.0");
+ private static final InetAddress OUTER_ADDR6 =
+ InetAddress.parseNumericAddress("2001:db8:f00d::1");
+ private static final InetAddress INNER_ADDR4 = InetAddress.parseNumericAddress("10.0.0.1");
+ private static final InetAddress INNER_ADDR6 =
+ InetAddress.parseNumericAddress("2001:db8:d00d::1");
+
+ private Network mUnderlyingNetwork;
+ private Network mIpSecNetwork;
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ protected void tearDown() {
+ setAppop(false);
+ }
+
+ private void setAppop(boolean allow) {
+ // Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted by the
+ // telephony framework, and the only permission that is sufficient is NETWORK_STACK. So we
+ // shell out the appop manager, to give us the right appop permissions.
+ String cmd =
+ "appops set "
+ + mContext.getPackageName()
+ + " MANAGE_IPSEC_TUNNELS "
+ + (allow ? "allow" : "deny");
+ SystemUtil.runShellCommand(cmd);
+ }
+
+ public void testSecurityExceptionsCreateTunnelInterface() throws Exception {
+ // Ensure we don't have the appop. Permission is not requested in the Manifest
+ setAppop(false);
+
+ // Security exceptions are thrown regardless of IPv4/IPv6. Just test one
+ try {
+ mISM.createIpSecTunnelInterface(OUTER_ADDR6, OUTER_ADDR6, mUnderlyingNetwork);
+ fail("Did not throw SecurityException for Tunnel creation without appop");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ public void testSecurityExceptionsBuildTunnelTransform() throws Exception {
+ // Ensure we don't have the appop. Permission is not requested in the Manifest
+ setAppop(false);
+
+ // Security exceptions are thrown regardless of IPv4/IPv6. Just test one
+ try (IpSecManager.SecurityParameterIndex spi =
+ mISM.allocateSecurityParameterIndex(OUTER_ADDR4);
+ IpSecTransform transform =
+ new IpSecTransform.Builder(mContext)
+ .buildTunnelModeTransform(OUTER_ADDR4, spi)) {
+ fail("Did not throw SecurityException for Transform creation without appop");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ private void checkTunnel(InetAddress inner, InetAddress outer, boolean useEncap)
+ throws Exception {
+ setAppop(true);
+ int innerPrefixLen = inner instanceof Inet6Address ? IP6_PREFIX_LEN : IP4_PREFIX_LEN;
+
+ try (IpSecManager.SecurityParameterIndex spi = mISM.allocateSecurityParameterIndex(outer);
+ IpSecManager.IpSecTunnelInterface tunnelIntf =
+ mISM.createIpSecTunnelInterface(outer, outer, mCM.getActiveNetwork());
+ IpSecManager.UdpEncapsulationSocket encapSocket =
+ mISM.openUdpEncapsulationSocket()) {
+
+ IpSecTransform.Builder transformBuilder = new IpSecTransform.Builder(mContext);
+ transformBuilder.setEncryption(
+ new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY));
+ transformBuilder.setAuthentication(
+ new IpSecAlgorithm(
+ IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4));
+
+ if (useEncap) {
+ transformBuilder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
+ }
+
+ // Check transform application
+ try (IpSecTransform transform = transformBuilder.buildTunnelModeTransform(outer, spi)) {
+ mISM.applyTunnelModeTransform(tunnelIntf, IpSecManager.DIRECTION_IN, transform);
+ mISM.applyTunnelModeTransform(tunnelIntf, IpSecManager.DIRECTION_OUT, transform);
+
+ // TODO: Test to ensure that send/receive works with these transforms.
+ }
+
+ // Check interface was created
+ NetworkInterface netIntf = NetworkInterface.getByName(tunnelIntf.getInterfaceName());
+ assertNotNull(netIntf);
+
+ // Add addresses and check
+ tunnelIntf.addAddress(inner, innerPrefixLen);
+ for (InterfaceAddress intfAddr : netIntf.getInterfaceAddresses()) {
+ assertEquals(intfAddr.getAddress(), inner);
+ assertEquals(intfAddr.getNetworkPrefixLength(), innerPrefixLen);
+ }
+
+ // Remove addresses and check
+ tunnelIntf.removeAddress(inner, innerPrefixLen);
+ assertTrue(netIntf.getInterfaceAddresses().isEmpty());
+
+ // Check interface was cleaned up
+ tunnelIntf.close();
+ netIntf = NetworkInterface.getByName(tunnelIntf.getInterfaceName());
+ assertNull(netIntf);
+ }
+ }
+
+ /*
+ * Create, add and remove addresses, then teardown tunnel
+ */
+ public void testTunnelV4InV4() throws Exception {
+ checkTunnel(INNER_ADDR4, OUTER_ADDR4, false);
+ }
+
+ public void testTunnelV4InV4UdpEncap() throws Exception {
+ checkTunnel(INNER_ADDR4, OUTER_ADDR4, true);
+ }
+
+ public void testTunnelV4InV6() throws Exception {
+ checkTunnel(INNER_ADDR4, OUTER_ADDR6, false);
+ }
+
+ public void testTunnelV6InV4() throws Exception {
+ checkTunnel(INNER_ADDR6, OUTER_ADDR4, false);
+ }
+
+ public void testTunnelV6InV4UdpEncap() throws Exception {
+ checkTunnel(INNER_ADDR6, OUTER_ADDR4, true);
+ }
+
+ public void testTunnelV6InV6() throws Exception {
+ checkTunnel(INNER_ADDR6, OUTER_ADDR6, false);
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/IpSecSysctlTest.java b/tests/cts/net/src/android/net/cts/IpSecSysctlTest.java
deleted file mode 100644
index b362282..0000000
--- a/tests/cts/net/src/android/net/cts/IpSecSysctlTest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.cts;
-
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-import android.system.StructStat;
-import android.test.AndroidTestCase;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-/**
- * Tests for multinetwork sysctl functionality.
- */
-public class IpSecSysctlTest extends SysctlBaseTest {
-
- // SPI expiration sysctls. Must be present and set greater than 1h.
- private static final String SPI_TIMEOUT_SYSCTL = "/proc/sys/net/core/xfrm_acq_expires";
- private static final int MIN_ACQ_EXPIRES = 3600;
-
- /**
- * Checks that SPI default timeouts are overridden, and set to a reasonable length of time
- */
- public void testProcFiles() throws ErrnoException, IOException, NumberFormatException {
- int value = getIntValue(SPI_TIMEOUT_SYSCTL);
- assertAtLeast(SPI_TIMEOUT_SYSCTL, value, MIN_ACQ_EXPIRES);
- }
-}
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index b2c9d9b..c3e65b7 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -43,6 +43,12 @@
private static native int runSetprocnetwork(long networkHandle);
private static native int runSetsocknetwork(long networkHandle);
private static native int runDatagramCheck(long networkHandle);
+ private static native int runResNapiMalformedCheck(long networkHandle);
+ private static native int runResNcancelCheck(long networkHandle);
+ private static native int runResNqueryCheck(long networkHandle);
+ private static native int runResNsendCheck(long networkHandle);
+
+
private ConnectivityManager mCM;
@@ -175,4 +181,14 @@
fail();
} catch (IllegalArgumentException e) {}
}
+
+ public void testResNApi() {
+ for (Network network : getTestableNetworks()) {
+ // Throws AssertionError directly in jni function if test fail.
+ runResNqueryCheck(network.getNetworkHandle());
+ runResNsendCheck(network.getNetworkHandle());
+ runResNcancelCheck(network.getNetworkHandle());
+ runResNapiMalformedCheck(network.getNetworkHandle());
+ }
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkSysctlTest.java b/tests/cts/net/src/android/net/cts/MultinetworkSysctlTest.java
deleted file mode 100644
index 1d0c111..0000000
--- a/tests/cts/net/src/android/net/cts/MultinetworkSysctlTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.cts;
-
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-import android.system.StructStat;
-import android.test.AndroidTestCase;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-/**
- * Tests for multinetwork sysctl functionality.
- */
-public class MultinetworkSysctlTest extends SysctlBaseTest {
-
- // Global sysctls. Must be present and set to 1.
- private static final String[] GLOBAL_SYSCTLS = {
- "/proc/sys/net/ipv4/fwmark_reflect",
- "/proc/sys/net/ipv6/fwmark_reflect",
- "/proc/sys/net/ipv4/tcp_fwmark_accept",
- };
-
- // Per-interface IPv6 autoconf sysctls.
- private static final String IPV6_SYSCTL_DIR = "/proc/sys/net/ipv6/conf";
- private static final String AUTOCONF_SYSCTL = "accept_ra_rt_table";
-
- /**
- * Checks that the sysctls for multinetwork kernel features are present and
- * enabled. The necessary kernel commits are:
- *
- * Mainline Linux:
- * e110861 net: add a sysctl to reflect the fwmark on replies
- * 1b3c61d net: Use fwmark reflection in PMTU discovery.
- * 84f39b0 net: support marking accepting TCP sockets
- *
- * Common Android tree (e.g., 3.10):
- * a03f539 net: ipv6: autoconf routes into per-device tables
- */
- public void testProcFiles() throws ErrnoException, IOException, NumberFormatException {
- for (String sysctl : GLOBAL_SYSCTLS) {
- int value = getIntValue(sysctl);
- assertEquals(sysctl, 1, value);
- }
-
- File[] interfaceDirs = new File(IPV6_SYSCTL_DIR).listFiles();
- for (File interfaceDir : interfaceDirs) {
- if (interfaceDir.getName().equals("all") || interfaceDir.getName().equals("lo")) {
- continue;
- }
- String sysctl = new File(interfaceDir, AUTOCONF_SYSCTL).getAbsolutePath();
- int value = getIntValue(sysctl);
- assertLess(sysctl, value, 0);
- }
- }
-}
diff --git a/tests/cts/net/src/android/net/cts/SysctlBaseTest.java b/tests/cts/net/src/android/net/cts/SysctlBaseTest.java
deleted file mode 100644
index a5966d4..0000000
--- a/tests/cts/net/src/android/net/cts/SysctlBaseTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.cts;
-
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-import android.system.StructStat;
-import android.test.AndroidTestCase;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-/**
- * Tests for multinetwork sysctl functionality.
- */
-public class SysctlBaseTest extends AndroidTestCase {
-
- // Expected mode, UID, and GID of sysctl files.
- private static final int SYSCTL_MODE = 0100644;
- private static final int SYSCTL_UID = 0;
- private static final int SYSCTL_GID = 0;
-
- private void checkSysctlPermissions(String fileName) throws ErrnoException {
- StructStat stat = Os.stat(fileName);
- assertEquals("mode of " + fileName + ":", SYSCTL_MODE, stat.st_mode);
- assertEquals("UID of " + fileName + ":", SYSCTL_UID, stat.st_uid);
- assertEquals("GID of " + fileName + ":", SYSCTL_GID, stat.st_gid);
- }
-
- protected void assertLess(String sysctl, int a, int b) {
- assertTrue("value of " + sysctl + ": expected < " + b + " but was: " + a, a < b);
- }
-
- protected void assertAtLeast(String sysctl, int a, int b) {
- assertTrue("value of " + sysctl + ": expected >= " + b + " but was: " + a, a >= b);
- }
-
- private String readFile(String fileName) throws ErrnoException, IOException {
- byte[] buf = new byte[1024];
- FileDescriptor fd = Os.open(fileName, 0, OsConstants.O_RDONLY);
- int bytesRead = Os.read(fd, buf, 0, buf.length);
- assertLess("length of " + fileName + ":", bytesRead, buf.length);
- return new String(buf);
- }
-
- /*
- * Checks permissions and retrieves the sysctl's value. Retrieval of value should always use
- * this method
- */
- protected int getIntValue(String filename) throws ErrnoException, IOException {
- checkSysctlPermissions(filename);
- return Integer.parseInt(readFile(filename).trim());
- }
-}
diff --git a/tests/cts/net/src/android/net/cts/TrafficStatsTest.java b/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
index a8743fa..503ba51 100755
--- a/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
+++ b/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
@@ -16,14 +16,14 @@
package android.net.cts;
+import android.content.pm.PackageManager;
+import android.net.NetworkStats;
import android.net.TrafficStats;
import android.os.Process;
+import android.os.SystemProperties;
import android.test.AndroidTestCase;
import android.util.Log;
-import java.io.BufferedReader;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -99,6 +99,7 @@
final int byteCount = 1024;
final int packetCount = 1024;
+ TrafficStats.startDataProfiling(null);
final ServerSocket server = new ServerSocket(0);
new Thread("TrafficStatsTest.testTrafficStatsForLocalhost") {
@Override
@@ -153,6 +154,7 @@
Thread.sleep(1000);
} catch (InterruptedException e) {
}
+ NetworkStats testStats = TrafficStats.stopDataProfiling(null);
long mobileTxPacketsAfter = TrafficStats.getMobileTxPackets();
long mobileRxPacketsAfter = TrafficStats.getMobileRxPackets();
@@ -194,6 +196,24 @@
Log.i(LOG_TAG, "lingering traffic data: " + deltaTxOtherPackets + "/" + deltaRxOtherPackets);
}
+ // Check the per uid stats read from data profiling have the stats expected. The data
+ // profiling snapshot is generated from readNetworkStatsDetail() method in
+ // networkStatsService and in this way we can verify the detail networkStats of a given uid
+ // is correct.
+ NetworkStats.Entry entry = testStats.getTotal(null, Process.myUid());
+ assertTrue("txPackets detail: " + entry.txPackets + " uidTxPackets: " + uidTxDeltaPackets,
+ entry.txPackets >= packetCount + minExpectedExtraPackets
+ && entry.txPackets <= uidTxDeltaPackets);
+ assertTrue("rxPackets detail: " + entry.rxPackets + " uidRxPackets: " + uidRxDeltaPackets,
+ entry.rxPackets >= packetCount + minExpectedExtraPackets
+ && entry.rxPackets <= uidRxDeltaPackets);
+ assertTrue("txBytes detail: " + entry.txBytes + " uidTxDeltaBytes: " + uidTxDeltaBytes,
+ entry.txBytes >= tcpPacketToIpBytes(packetCount, byteCount)
+ + tcpPacketToIpBytes(minExpectedExtraPackets, 0) && entry.txBytes <= uidTxDeltaBytes);
+ assertTrue("rxBytes detail: " + entry.rxBytes + " uidRxDeltaBytes: " + uidRxDeltaBytes,
+ entry.rxBytes >= tcpPacketToIpBytes(packetCount, byteCount)
+ + tcpPacketToIpBytes(minExpectedExtraPackets, 0) && entry.rxBytes <= uidRxDeltaBytes);
+
assertTrue("uidtxp: " + uidTxPacketsBefore + " -> " + uidTxPacketsAfter + " delta=" + uidTxDeltaPackets +
" Wanted: " + uidTxDeltaPackets + ">=" + packetCount + "+" + minExpectedExtraPackets + " && " +
uidTxDeltaPackets + "<=" + packetCount + "+" + packetCount + "+" + maxExpectedExtraPackets + "+" + deltaTxOtherPackets,
@@ -216,19 +236,37 @@
uidRxDeltaBytes <= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets + deltaRxOtherPackets, 0));
// Localhost traffic *does* count against total stats.
- // Fudge by 132 packets of 1500 bytes not related to the test.
+ // Check the total stats increased after test data transfer over localhost has been made.
assertTrue("ttxp: " + totalTxPacketsBefore + " -> " + totalTxPacketsAfter,
- totalTxPacketsAfter >= totalTxPacketsBefore + uidTxDeltaPackets &&
- totalTxPacketsAfter <= totalTxPacketsBefore + uidTxDeltaPackets + 132);
+ totalTxPacketsAfter >= totalTxPacketsBefore + uidTxDeltaPackets);
assertTrue("trxp: " + totalRxPacketsBefore + " -> " + totalRxPacketsAfter,
- totalRxPacketsAfter >= totalRxPacketsBefore + uidRxDeltaPackets &&
- totalRxPacketsAfter <= totalRxPacketsBefore + uidRxDeltaPackets + 132);
+ totalRxPacketsAfter >= totalRxPacketsBefore + uidRxDeltaPackets);
assertTrue("ttxb: " + totalTxBytesBefore + " -> " + totalTxBytesAfter,
- totalTxBytesAfter >= totalTxBytesBefore + uidTxDeltaBytes &&
- totalTxBytesAfter <= totalTxBytesBefore + uidTxDeltaBytes + 132 * 1500);
+ totalTxBytesAfter >= totalTxBytesBefore + uidTxDeltaBytes);
assertTrue("trxb: " + totalRxBytesBefore + " -> " + totalRxBytesAfter,
- totalRxBytesAfter >= totalRxBytesBefore + uidRxDeltaBytes &&
- totalRxBytesAfter <= totalRxBytesBefore + uidRxDeltaBytes + 132 * 1500);
+ totalRxBytesAfter >= totalRxBytesBefore + uidRxDeltaBytes);
+
+ // If the adb TCP port is opened, this test may be run by adb over network.
+ // Huge amount of data traffic might go through the network and accounted into total packets
+ // stats. The upper bound check would be meaningless.
+ // TODO: Consider precisely calculate the traffic accounted due to adb over network and
+ // subtract it when checking upper bound instead of skip checking.
+ final PackageManager pm = mContext.getPackageManager();
+ if (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1
+ || SystemProperties.getInt("service.adb.tcp.port", -1) > -1
+ || !pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY)) {
+ Log.i(LOG_TAG, "adb is running over the network, skip the upper bound check");
+ } else {
+ // Fudge by 132 packets of 1500 bytes not related to the test.
+ assertTrue("ttxp: " + totalTxPacketsBefore + " -> " + totalTxPacketsAfter,
+ totalTxPacketsAfter <= totalTxPacketsBefore + uidTxDeltaPackets + 132);
+ assertTrue("trxp: " + totalRxPacketsBefore + " -> " + totalRxPacketsAfter,
+ totalRxPacketsAfter <= totalRxPacketsBefore + uidRxDeltaPackets + 132);
+ assertTrue("ttxb: " + totalTxBytesBefore + " -> " + totalTxBytesAfter,
+ totalTxBytesAfter <= totalTxBytesBefore + uidTxDeltaBytes + 132 * 1500);
+ assertTrue("trxb: " + totalRxBytesBefore + " -> " + totalRxBytesAfter,
+ totalRxBytesAfter <= totalRxBytesBefore + uidRxDeltaBytes + 132 * 1500);
+ }
// Localhost traffic should *not* count against mobile stats,
// There might be some other traffic, but nowhere near 1MB.
@@ -244,6 +282,5 @@
assertTrue("mrxb: " + mobileRxBytesBefore + " -> " + mobileRxBytesAfter,
mobileRxBytesAfter >= mobileRxBytesBefore &&
mobileRxBytesAfter <= mobileRxBytesBefore + 200000);
-
}
}
diff --git a/tests/cts/net/src/android/net/cts/UriTest.java b/tests/cts/net/src/android/net/cts/UriTest.java
index 5c54cda..40b8fb7 100644
--- a/tests/cts/net/src/android/net/cts/UriTest.java
+++ b/tests/cts/net/src/android/net/cts/UriTest.java
@@ -22,6 +22,7 @@
import android.test.AndroidTestCase;
import java.io.File;
import java.util.Arrays;
+import java.util.ArrayList;
public class UriTest extends AndroidTestCase {
public void testParcelling() {
@@ -73,6 +74,12 @@
assertEquals("new", b.getFragment());
assertEquals("bar", b.getSchemeSpecificPart());
assertEquals("foo", b.getScheme());
+
+ a = Uri.fromParts("scheme", "[2001:db8::dead:e1f]/foo", "bar");
+ b = a.buildUpon().fragment("qux").build();
+ assertEquals("qux", b.getFragment());
+ assertEquals("[2001:db8::dead:e1f]/foo", b.getSchemeSpecificPart());
+ assertEquals("scheme", b.getScheme());
}
public void testStringUri() {
@@ -120,6 +127,38 @@
assertEquals("a.foo.com", uri.getHost());
assertEquals(-1, uri.getPort());
assertEquals("\\.example.com/path", uri.getPath());
+
+ uri = Uri.parse("https://[2001:db8::dead:e1f]/foo");
+ assertEquals("[2001:db8::dead:e1f]", uri.getAuthority());
+ assertNull(uri.getUserInfo());
+ assertEquals("[2001:db8::dead:e1f]", uri.getHost());
+ assertEquals(-1, uri.getPort());
+ assertEquals("/foo", uri.getPath());
+ assertEquals(null, uri.getFragment());
+ assertEquals("//[2001:db8::dead:e1f]/foo", uri.getSchemeSpecificPart());
+
+ uri = Uri.parse("https://[2001:db8::dead:e1f]/#foo");
+ assertEquals("[2001:db8::dead:e1f]", uri.getAuthority());
+ assertNull(uri.getUserInfo());
+ assertEquals("[2001:db8::dead:e1f]", uri.getHost());
+ assertEquals(-1, uri.getPort());
+ assertEquals("/", uri.getPath());
+ assertEquals("foo", uri.getFragment());
+ assertEquals("//[2001:db8::dead:e1f]/", uri.getSchemeSpecificPart());
+
+ uri = Uri.parse(
+ "https://some:user@[2001:db8::dead:e1f]:1234/foo?corge=thud&corge=garp#bar");
+ assertEquals("some:user@[2001:db8::dead:e1f]:1234", uri.getAuthority());
+ assertEquals("some:user", uri.getUserInfo());
+ assertEquals("[2001:db8::dead:e1f]", uri.getHost());
+ assertEquals(1234, uri.getPort());
+ assertEquals("/foo", uri.getPath());
+ assertEquals("bar", uri.getFragment());
+ assertEquals("//some:user@[2001:db8::dead:e1f]:1234/foo?corge=thud&corge=garp",
+ uri.getSchemeSpecificPart());
+ assertEquals("corge=thud&corge=garp", uri.getQuery());
+ assertEquals("thud", uri.getQueryParameter("corge"));
+ assertEquals(Arrays.asList("thud", "garp"), uri.getQueryParameters("corge"));
}
public void testCompareTo() {
@@ -164,22 +203,62 @@
String encoded = Uri.encode("Bob:/", "/");
assertEquals(-1, encoded.indexOf(':'));
assertTrue(encoded.indexOf('/') > -1);
- assertDecode(null);
- assertDecode("");
- assertDecode("Bob");
- assertDecode(":Bob");
- assertDecode("::Bob");
- assertDecode("Bob::Lee");
- assertDecode("Bob:Lee");
- assertDecode("Bob::");
- assertDecode("Bob:");
- assertDecode("::Bob::");
+ assertEncodeDecodeRoundtripExact(null);
+ assertEncodeDecodeRoundtripExact("");
+ assertEncodeDecodeRoundtripExact("Bob");
+ assertEncodeDecodeRoundtripExact(":Bob");
+ assertEncodeDecodeRoundtripExact("::Bob");
+ assertEncodeDecodeRoundtripExact("Bob::Lee");
+ assertEncodeDecodeRoundtripExact("Bob:Lee");
+ assertEncodeDecodeRoundtripExact("Bob::");
+ assertEncodeDecodeRoundtripExact("Bob:");
+ assertEncodeDecodeRoundtripExact("::Bob::");
+ assertEncodeDecodeRoundtripExact("https:/some:user@[2001:db8::dead:e1f]:1234/foo#bar");
}
- private void assertDecode(String s) {
+ private static void assertEncodeDecodeRoundtripExact(String s) {
assertEquals(s, Uri.decode(Uri.encode(s, null)));
}
+ public void testDecode_emptyString_returnsEmptyString() {
+ assertEquals("", Uri.decode(""));
+ }
+
+ public void testDecode_null_returnsNull() {
+ assertNull(Uri.decode(null));
+ }
+
+ public void testDecode_wrongHexDigit() {
+ // %p in the end.
+ assertEquals("ab/$\u0102%\u0840\uFFFD\u0000", Uri.decode("ab%2f$%C4%82%25%e0%a1%80%p"));
+ }
+
+ public void testDecode_secondHexDigitWrong() {
+ // %1p in the end.
+ assertEquals("ab/$\u0102%\u0840\uFFFD\u0001", Uri.decode("ab%2f$%c4%82%25%e0%a1%80%1p"));
+ }
+
+ public void testDecode_endsWithPercent_appendsUnknownCharacter() {
+ // % in the end.
+ assertEquals("ab/$\u0102%\u0840\uFFFD", Uri.decode("ab%2f$%c4%82%25%e0%a1%80%"));
+ }
+
+ public void testDecode_plusNotConverted() {
+ assertEquals("ab/$\u0102%+\u0840", Uri.decode("ab%2f$%c4%82%25+%e0%a1%80"));
+ }
+
+ // Last character needs decoding (make sure we are flushing the buffer with chars to decode).
+ public void testDecode_lastCharacter() {
+ assertEquals("ab/$\u0102%\u0840", Uri.decode("ab%2f$%c4%82%25%e0%a1%80"));
+ }
+
+ // Check that a second row of encoded characters is decoded properly (internal buffers are
+ // reset properly).
+ public void testDecode_secondRowOfEncoded() {
+ assertEquals("ab/$\u0102%\u0840aa\u0840",
+ Uri.decode("ab%2f$%c4%82%25%e0%a1%80aa%e0%a1%80"));
+ }
+
public void testFromFile() {
File f = new File("/tmp/bob");
Uri uri = Uri.fromFile(f);
@@ -425,4 +504,87 @@
Uri.parse("HTTP://USER@WWW.ANDROID.COM:100/ABOUT?foo=blah@bar=bleh#c")
.normalizeScheme());
}
+
+ public void testToSafeString_tel() {
+ checkToSafeString("tel:xxxxxx", "tel:Google");
+ checkToSafeString("tel:xxxxxxxxxx", "tel:1234567890");
+ checkToSafeString("tEl:xxx.xxx-xxxx", "tEl:123.456-7890");
+ }
+
+ public void testToSafeString_sip() {
+ checkToSafeString("sip:xxxxxxx@xxxxxxx.xxxxxxxx", "sip:android@android.com:1234");
+ checkToSafeString("sIp:xxxxxxx@xxxxxxx.xxx", "sIp:android@android.com");
+ }
+
+ public void testToSafeString_sms() {
+ checkToSafeString("sms:xxxxxx", "sms:123abc");
+ checkToSafeString("smS:xxx.xxx-xxxx", "smS:123.456-7890");
+ }
+
+ public void testToSafeString_smsto() {
+ checkToSafeString("smsto:xxxxxx", "smsto:123abc");
+ checkToSafeString("SMSTo:xxx.xxx-xxxx", "SMSTo:123.456-7890");
+ }
+
+ public void testToSafeString_mailto() {
+ checkToSafeString("mailto:xxxxxxx@xxxxxxx.xxx", "mailto:android@android.com");
+ checkToSafeString("Mailto:xxxxxxx@xxxxxxx.xxxxxxxxxx",
+ "Mailto:android@android.com/secret");
+ }
+
+ public void testToSafeString_nfc() {
+ checkToSafeString("nfc:xxxxxx", "nfc:123abc");
+ checkToSafeString("nfc:xxx.xxx-xxxx", "nfc:123.456-7890");
+ checkToSafeString("nfc:xxxxxxx@xxxxxxx.xxx", "nfc:android@android.com");
+ }
+
+ public void testToSafeString_http() {
+ checkToSafeString("http://www.android.com/...", "http://www.android.com");
+ checkToSafeString("HTTP://www.android.com/...", "HTTP://www.android.com");
+ checkToSafeString("http://www.android.com/...", "http://www.android.com/");
+ checkToSafeString("http://www.android.com/...", "http://www.android.com/secretUrl?param");
+ checkToSafeString("http://www.android.com/...",
+ "http://user:pwd@www.android.com/secretUrl?param");
+ checkToSafeString("http://www.android.com/...",
+ "http://user@www.android.com/secretUrl?param");
+ checkToSafeString("http://www.android.com/...", "http://www.android.com/secretUrl?param");
+ checkToSafeString("http:///...", "http:///path?param");
+ checkToSafeString("http:///...", "http://");
+ checkToSafeString("http://:12345/...", "http://:12345/");
+ }
+
+ public void testToSafeString_https() {
+ checkToSafeString("https://www.android.com/...", "https://www.android.com/secretUrl?param");
+ checkToSafeString("https://www.android.com:8443/...",
+ "https://user:pwd@www.android.com:8443/secretUrl?param");
+ checkToSafeString("https://www.android.com/...", "https://user:pwd@www.android.com");
+ checkToSafeString("Https://www.android.com/...", "Https://user:pwd@www.android.com");
+ }
+
+ public void testToSafeString_ftp() {
+ checkToSafeString("ftp://ftp.android.com/...", "ftp://ftp.android.com/");
+ checkToSafeString("ftP://ftp.android.com/...", "ftP://anonymous@ftp.android.com/");
+ checkToSafeString("ftp://ftp.android.com:2121/...",
+ "ftp://root:love@ftp.android.com:2121/");
+ }
+
+ public void testToSafeString_rtsp() {
+ checkToSafeString("rtsp://rtsp.android.com/...", "rtsp://rtsp.android.com/");
+ checkToSafeString("rtsp://rtsp.android.com/...", "rtsp://rtsp.android.com/video.mov");
+ checkToSafeString("rtsp://rtsp.android.com/...", "rtsp://rtsp.android.com/video.mov?param");
+ checkToSafeString("RtsP://rtsp.android.com/...", "RtsP://anonymous@rtsp.android.com/");
+ checkToSafeString("rtsp://rtsp.android.com:2121/...",
+ "rtsp://username:password@rtsp.android.com:2121/");
+ }
+
+ public void testToSafeString_notSupport() {
+ checkToSafeString("unsupported://ajkakjah/askdha/secret?secret",
+ "unsupported://ajkakjah/askdha/secret?secret");
+ checkToSafeString("unsupported:ajkakjah/askdha/secret?secret",
+ "unsupported:ajkakjah/askdha/secret?secret");
+ }
+
+ private void checkToSafeString(String expectedSafeString, String original) {
+ assertEquals(expectedSafeString, Uri.parse(original).toSafeString());
+ }
}
diff --git a/tests/cts/net/src/android/net/http/cts/SslCertificateTest.java b/tests/cts/net/src/android/net/http/cts/SslCertificateTest.java
index 70ae496..95f415c 100644
--- a/tests/cts/net/src/android/net/http/cts/SslCertificateTest.java
+++ b/tests/cts/net/src/android/net/http/cts/SslCertificateTest.java
@@ -230,6 +230,19 @@
final String EXPECTED = "Issued to: c=ccc,o=testOName,ou=testUName,cn=testCName;\n"
+ "Issued by: e=aeei,c=adb,o=testOName,ou=testUName,cn=testCName;\n";
assertEquals(EXPECTED, ssl.toString());
+ assertNull(ssl.getX509Certificate());
}
+ public void testGetX509Certificate() {
+ final String TO = "c=ccc,o=testOName,ou=testUName,cn=testCName";
+ final String BY = "e=aeei,c=adb,o=testOName,ou=testUName,cn=testCName";
+ Date validNotBefore = new Date(System.currentTimeMillis() - 1000);
+ Date validNotAfter = new Date(System.currentTimeMillis());
+ SslCertificate ssl = new SslCertificate(TO, BY, validNotBefore, validNotAfter);
+ assertNull(ssl.getX509Certificate());
+
+ X509Certificate cert = new MockX509Certificate();
+ ssl = new SslCertificate(cert);
+ assertSame(cert, ssl.getX509Certificate());
+ }
}
diff --git a/tests/cts/net/src/android/net/ipv6/cts/PingTest.java b/tests/cts/net/src/android/net/ipv6/cts/PingTest.java
index c23ad30..146fd83 100644
--- a/tests/cts/net/src/android/net/ipv6/cts/PingTest.java
+++ b/tests/cts/net/src/android/net/ipv6/cts/PingTest.java
@@ -61,7 +61,7 @@
/** The beginning of an ICMPv6 echo request: type, code, and uninitialized checksum. */
private static final byte[] PING_HEADER = new byte[] {
- (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00
+ (byte) ICMP6_ECHO_REQUEST, (byte) 0x00, (byte) 0x00, (byte) 0x00
};
/**
@@ -135,7 +135,7 @@
byte[] response = new byte[bytesRead];
responseBuffer.flip();
responseBuffer.get(response, 0, bytesRead);
- assertEquals((byte) 0x81, response[0]);
+ assertEquals((byte) ICMP6_ECHO_REPLY, response[0]);
// Find out what ICMP ID was used in the packet that was sent.
int id = ((InetSocketAddress) Os.getsockname(s)).getPort();