Merge "[DK4] Add cts to test automatic on/off keepalive"

This commit is contained in:
Chiachang Wang 2023-03-10 02:48:21 +00:00 • committed by Gerrit Code Review
commit 4bc39e2d22
3 changed files with 215 additions and 0 deletions

View file

@ -8,6 +8,9 @@
},
{
"exclude-annotation": "android.platform.test.annotations.FlakyTest"
},
{
"exclude-annotation": "android.platform.test.annotations.RequiresDevice"
}
]
}

View file

@ -18,6 +18,8 @@ package com.android.cts.net.hostside;
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.READ_DEVICE_CONFIG;
import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
import static android.content.pm.PackageManager.FEATURE_WIFI;
import static android.net.ConnectivityManager.TYPE_VPN;
@ -36,6 +38,12 @@ import static android.test.MoreAsserts.assertNotEqual;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_DATA_RECEIVED;
import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_ERROR;
import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_PAUSED;
import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_RESUMED;
import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_STARTED;
import static com.android.cts.net.hostside.VpnTest.TestSocketKeepaliveCallback.CallbackType.ON_STOPPED;
import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN;
import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE;
import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_EXPORTED;
@ -64,6 +72,7 @@ import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.IpSecManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
@ -71,6 +80,7 @@ import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.Proxy;
import android.net.ProxyInfo;
import android.net.SocketKeepalive;
import android.net.TestNetworkInterface;
import android.net.TestNetworkManager;
import android.net.TransportInfo;
@ -79,6 +89,7 @@ import android.net.VpnManager;
import android.net.VpnService;
import android.net.VpnTransportInfo;
import android.net.cts.util.CtsNetUtils;
import android.net.util.KeepaliveUtils;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Handler;
@ -87,6 +98,7 @@ import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
@ -105,6 +117,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.compatibility.common.util.BlockingBroadcastReceiver;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.ArrayTrackRecord;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.PacketBuilder;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@ -141,6 +155,7 @@ import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@ -190,6 +205,12 @@ public class VpnTest {
public static int SOCKET_TIMEOUT_MS = 100;
public static String TEST_HOST = "connectivitycheck.gstatic.com";
private static final String AUTOMATIC_ON_OFF_KEEPALIVE_VERSION =
"automatic_on_off_keepalive_version";
// Enabled since version 1 means it's always enabled because the version is always above 1
private static final String AUTOMATIC_ON_OFF_KEEPALIVE_ENABLED = "1";
private static final long TEST_TCP_POLLING_TIMER_EXPIRED_PERIOD_MS = 60_000L;
private UiDevice mDevice;
private MyActivity mActivity;
private String mPackageName;
@ -1041,6 +1062,183 @@ public class VpnTest {
checkStrictModePrivateDns();
}
private int getSupportedKeepalives(NetworkCapabilities nc) throws Exception {
// Get number of supported concurrent keepalives for testing network.
final int[] keepalivesPerTransport = KeepaliveUtils.getSupportedKeepalives(
mTargetContext);
return KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
keepalivesPerTransport, nc);
}
// This class can't be private, otherwise the constants can't be static imported.
static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback {
// This must be larger than the alarm delay in AutomaticOnOffKeepaliveTracker.
private static final int KEEPALIVE_TIMEOUT_MS = 10_000;
public enum CallbackType {
ON_STARTED,
ON_RESUMED,
ON_STOPPED,
ON_PAUSED,
ON_ERROR,
ON_DATA_RECEIVED
}
private ArrayTrackRecord<CallbackType> mHistory = new ArrayTrackRecord<>();
private ArrayTrackRecord<CallbackType>.ReadHead mEvents = mHistory.newReadHead();
@Override
public void onStarted() {
mHistory.add(ON_STARTED);
}
@Override
public void onResumed() {
mHistory.add(ON_RESUMED);
}
@Override
public void onStopped() {
mHistory.add(ON_STOPPED);
}
@Override
public void onPaused() {
mHistory.add(ON_PAUSED);
}
@Override
public void onError(final int error) {
mHistory.add(ON_ERROR);
}
@Override
public void onDataReceived() {
mHistory.add(ON_DATA_RECEIVED);
}
public CallbackType poll() {
return mEvents.poll(KEEPALIVE_TIMEOUT_MS, it -> true);
}
}
private InetAddress getV4AddrByName(final String hostname) throws Exception {
final InetAddress[] allAddrs = InetAddress.getAllByName(hostname);
for (InetAddress addr : allAddrs) {
if (addr instanceof Inet4Address) return addr;
}
return null;
}
@Test
public void testAutomaticOnOffKeepaliveModeNoClose() throws Exception {
doTestAutomaticOnOffKeepaliveMode(false);
}
@Test
public void testAutomaticOnOffKeepaliveModeClose() throws Exception {
doTestAutomaticOnOffKeepaliveMode(true);
}
private void startKeepalive(SocketKeepalive kp, TestSocketKeepaliveCallback callback) {
runWithShellPermissionIdentity(() -> {
// Only SocketKeepalive.start() requires READ_DEVICE_CONFIG because feature is protected
// by a feature flag. But also verify ON_STARTED callback received here to ensure
// keepalive is indeed started because start() runs in the executor thread and shell
// permission may be dropped before reading DeviceConfig.
kp.start(10 /* intervalSec */, SocketKeepalive.FLAG_AUTOMATIC_ON_OFF, mNetwork);
// Verify callback status.
assertEquals(ON_STARTED, callback.poll());
}, READ_DEVICE_CONFIG);
}
private void doTestAutomaticOnOffKeepaliveMode(final boolean closeSocket) throws Exception {
assumeTrue(supportedHardware());
// Get default network first before starting VPN
final Network defaultNetwork = mCM.getActiveNetwork();
final TestableNetworkCallback cb = new TestableNetworkCallback();
registerDefaultNetworkCallback(cb);
cb.expect(CallbackEntry.AVAILABLE, defaultNetwork);
final NetworkCapabilities cap =
cb.expect(CallbackEntry.NETWORK_CAPS_UPDATED, defaultNetwork).getCaps();
final LinkProperties lp =
cb.expect(CallbackEntry.LINK_PROPERTIES_CHANGED, defaultNetwork).getLp();
cb.expect(CallbackEntry.BLOCKED_STATUS, defaultNetwork);
// Setup VPN
final FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
final 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, "" /* disallowedApplications */, null /* proxyInfo */,
null /* underlyingNetworks */, false /* isAlwaysMetered */);
assertSocketClosed(fd, TEST_HOST);
// Decrease the TCP polling timer for testing.
runWithShellPermissionIdentity(() -> mCM.setTestLowTcpPollingTimerForKeepalive(
System.currentTimeMillis() + TEST_TCP_POLLING_TIMER_EXPIRED_PERIOD_MS),
NETWORK_SETTINGS);
// Setup keepalive
final int supported = getSupportedKeepalives(cap);
assumeTrue("Network " + defaultNetwork + " does not support keepalive", supported != 0);
final InetAddress srcAddr = CollectionUtils.findFirst(lp.getAddresses(),
it -> it instanceof Inet4Address);
assumeTrue("This test requires native IPv4", srcAddr != null);
final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
final String origMode = runWithShellPermissionIdentity(() -> {
final String mode = DeviceConfig.getProperty(
DeviceConfig.NAMESPACE_CONNECTIVITY, AUTOMATIC_ON_OFF_KEEPALIVE_VERSION);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CONNECTIVITY,
AUTOMATIC_ON_OFF_KEEPALIVE_VERSION,
AUTOMATIC_ON_OFF_KEEPALIVE_ENABLED, false /* makeDefault */);
return mode;
}, READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG);
final IpSecManager ipSec = mTargetContext.getSystemService(IpSecManager.class);
SocketKeepalive kp = null;
try (IpSecManager.UdpEncapsulationSocket nattSocket = ipSec.openUdpEncapsulationSocket()) {
final InetAddress dstAddr = getV4AddrByName(TEST_HOST);
assertNotNull(dstAddr);
// Start keepalive with dynamic keepalive mode enabled.
final Executor executor = mTargetContext.getMainExecutor();
kp = mCM.createSocketKeepalive(defaultNetwork, nattSocket,
srcAddr, dstAddr, executor, callback);
startKeepalive(kp, callback);
// There should be no open sockets on the VPN network, because any
// open sockets were closed when startVpn above was called. So the
// first TCP poll should trigger ON_PAUSED.
assertEquals(ON_PAUSED, callback.poll());
final Socket s = new Socket();
mNetwork.bindSocket(s);
s.connect(new InetSocketAddress(dstAddr, 80));
assertEquals(ON_RESUMED, callback.poll());
if (closeSocket) {
s.close();
assertEquals(ON_PAUSED, callback.poll());
}
kp.stop();
assertEquals(ON_STOPPED, callback.poll());
} finally {
if (kp != null) kp.stop();
runWithShellPermissionIdentity(() -> {
DeviceConfig.setProperty(
DeviceConfig.NAMESPACE_CONNECTIVITY,
AUTOMATIC_ON_OFF_KEEPALIVE_VERSION,
origMode, false);
mCM.setTestLowTcpPollingTimerForKeepalive(0);
}, WRITE_DEVICE_CONFIG, NETWORK_SETTINGS);
}
}
@Test
public void testAppDisallowed() throws Exception {
assumeTrue(supportedHardware());

View file

@ -16,6 +16,8 @@
package com.android.cts.net;
import android.platform.test.annotations.RequiresDevice;
public class HostsideVpnTests extends HostsideNetworkTestCase {
@Override
@ -89,6 +91,18 @@ public class HostsideVpnTests extends HostsideNetworkTestCase {
TEST_PKG, TEST_PKG + ".VpnTest", "testAlwaysMeteredVpnWithNullUnderlyingNetwork");
}
@RequiresDevice // Keepalive is not supported on virtual hardware
public void testAutomaticOnOffKeepaliveModeClose() throws Exception {
runDeviceTests(
TEST_PKG, TEST_PKG + ".VpnTest", "testAutomaticOnOffKeepaliveModeClose");
}
@RequiresDevice // Keepalive is not supported on virtual hardware
public void testAutomaticOnOffKeepaliveModeNoClose() throws Exception {
runDeviceTests(
TEST_PKG, TEST_PKG + ".VpnTest", "testAutomaticOnOffKeepaliveModeNoClose");
}
public void testAlwaysMeteredVpnWithNonNullUnderlyingNetwork() throws Exception {
runDeviceTests(
TEST_PKG,