Merge "[KA09] add cts test for tcp keepalive offload" am: e8fb173b7d am: 25a820bdb9
am: da0ae30f64
Change-Id: I81e91394950f6b88ff10d13c67ce974e5cdd9a39
This commit is contained in:
@@ -23,12 +23,17 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
|
||||
import static android.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;
|
||||
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;
|
||||
@@ -65,11 +72,13 @@ import com.android.internal.telephony.PhoneConstants;
|
||||
|
||||
import libcore.io.Streams;
|
||||
|
||||
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,6 +89,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
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;
|
||||
@@ -97,7 +107,11 @@ public class ConnectivityManagerTest extends AndroidTestCase {
|
||||
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;
|
||||
@@ -127,7 +141,8 @@ public class ConnectivityManagerTest extends AndroidTestCase {
|
||||
new HashMap<Integer, NetworkConfig>();
|
||||
boolean mWifiConnectAttempted;
|
||||
private TestNetworkCallback mCellNetworkCallback;
|
||||
|
||||
private UiAutomation mUiAutomation;
|
||||
private boolean mShellPermissionIdentityAdopted;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
@@ -154,6 +169,8 @@ public class ConnectivityManagerTest extends AndroidTestCase {
|
||||
mNetworks.put(n.type, n);
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
mUiAutomation = mInstrumentation.getUiAutomation();
|
||||
mShellPermissionIdentityAdopted = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -165,6 +182,7 @@ public class ConnectivityManagerTest extends AndroidTestCase {
|
||||
if (cellConnectAttempted()) {
|
||||
disconnectFromCell();
|
||||
}
|
||||
dropShellPermissionIdentity();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
@@ -1038,4 +1056,226 @@ public class ConnectivityManagerTest extends AndroidTestCase {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user