Add cts test for VPN incoming packets blocking

Bug: 206482423
Test: atest HostsideVpnTests#testBlockIncomingPackets --iterations 30
Change-Id: I5a9c515c163f0f19e261fe3468e246ae299fa261
This commit is contained in:
Motomu Utsumi 2022-05-24 02:02:11 +00:00
commit 2e84a63561
5 changed files with 224 additions and 5 deletions

View file

@ -20,6 +20,7 @@ import android.os.ParcelFileDescriptor;
interface IRemoteSocketFactory {
ParcelFileDescriptor openSocketFd(String host, int port, int timeoutMs);
ParcelFileDescriptor openDatagramSocketFd();
String getPackageName();
int getUid();
}

View file

@ -83,9 +83,19 @@ public class RemoteSocketFactoryClient {
public FileDescriptor openSocketFd(String host, int port, int timeoutMs)
throws RemoteException, ErrnoException, IOException {
// Dup the filedescriptor so ParcelFileDescriptor's finalizer doesn't garbage collect it
// and cause our fd to become invalid. http://b/35927643 .
ParcelFileDescriptor pfd = mService.openSocketFd(host, port, timeoutMs);
FileDescriptor fd = Os.dup(pfd.getFileDescriptor());
// and cause fd to become invalid. http://b/35927643.
final ParcelFileDescriptor pfd = mService.openSocketFd(host, port, timeoutMs);
final FileDescriptor fd = Os.dup(pfd.getFileDescriptor());
pfd.close();
return fd;
}
public FileDescriptor openDatagramSocketFd()
throws RemoteException, ErrnoException, IOException {
// Dup the filedescriptor so ParcelFileDescriptor's finalizer doesn't garbage collect it
// and cause fd to become invalid. http://b/35927643.
final ParcelFileDescriptor pfd = mService.openDatagramSocketFd();
final FileDescriptor fd = Os.dup(pfd.getFileDescriptor());
pfd.close();
return fd;
}

View file

@ -16,6 +16,7 @@
package com.android.cts.net.hostside;
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
import static android.content.pm.PackageManager.FEATURE_WIFI;
@ -35,6 +36,8 @@ 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.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN;
import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE;
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
@ -59,12 +62,15 @@ import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.Proxy;
import android.net.ProxyInfo;
import android.net.TestNetworkInterface;
import android.net.TestNetworkManager;
import android.net.TransportInfo;
import android.net.Uri;
import android.net.VpnManager;
@ -72,6 +78,7 @@ import android.net.VpnService;
import android.net.VpnTransportInfo;
import android.net.cts.util.CtsNetUtils;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
@ -90,11 +97,13 @@ import android.telephony.TelephonyManager;
import android.test.MoreAsserts;
import android.text.TextUtils;
import android.util.Log;
import android.util.Range;
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.PacketBuilder;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.RecorderCallback;
@ -113,12 +122,14 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@ -164,6 +175,12 @@ public class VpnTest {
private static final String PRIVATE_DNS_SPECIFIER_SETTING = "private_dns_specifier";
private static final int NETWORK_CALLBACK_TIMEOUT_MS = 30_000;
private static final LinkAddress TEST_IP4_DST_ADDR = new LinkAddress("198.51.100.1/24");
private static final LinkAddress TEST_IP4_SRC_ADDR = new LinkAddress("198.51.100.2/24");
private static final LinkAddress TEST_IP6_DST_ADDR = new LinkAddress("2001:db8:1:3::1/64");
private static final LinkAddress TEST_IP6_SRC_ADDR = new LinkAddress("2001:db8:1:3::2/64");
private static final short TEST_SRC_PORT = 5555;
public static String TAG = "VpnTest";
public static int TIMEOUT_MS = 3 * 1000;
public static int SOCKET_TIMEOUT_MS = 100;
@ -1572,4 +1589,180 @@ public class VpnTest {
return future.get(timeout, unit);
}
}
private static final boolean EXPECT_PASS = false;
private static final boolean EXPECT_BLOCK = true;
@Test @IgnoreUpTo(Build.VERSION_CODES.R)
public void testBlockIncomingPackets() throws Exception {
assumeTrue(supportedHardware());
final Network network = mCM.getActiveNetwork();
assertNotNull("Requires a working Internet connection", network);
final int remoteUid = mRemoteSocketFactoryClient.getUid();
final List<Range<Integer>> lockdownRange = List.of(new Range<>(remoteUid, remoteUid));
final DetailedBlockedStatusCallback remoteUidCallback = new DetailedBlockedStatusCallback();
// Create a TUN interface
final FileDescriptor tunFd = runWithShellPermissionIdentity(() -> {
final TestNetworkManager tnm = getInstrumentation().getContext().getSystemService(
TestNetworkManager.class);
final TestNetworkInterface iface = tnm.createTunInterface(List.of(
TEST_IP4_DST_ADDR, TEST_IP6_DST_ADDR));
return iface.getFileDescriptor().getFileDescriptor();
}, MANAGE_TEST_NETWORKS);
// Create a remote UDP socket
final FileDescriptor remoteUdpFd = mRemoteSocketFactoryClient.openDatagramSocketFd();
testAndCleanup(() -> {
runWithShellPermissionIdentity(() -> {
mCM.registerDefaultNetworkCallbackForUid(remoteUid, remoteUidCallback,
new Handler(Looper.getMainLooper()));
}, NETWORK_SETTINGS);
remoteUidCallback.expectAvailableCallbacks(network);
// The remote UDP socket can receive packets coming from the TUN interface
checkBlockIncomingPacket(tunFd, remoteUdpFd, EXPECT_PASS);
// Lockdown uid that has the remote UDP socket
runWithShellPermissionIdentity(() -> {
mCM.setRequireVpnForUids(true /* requireVpn */, lockdownRange);
}, NETWORK_SETTINGS);
// setRequireVpnForUids setup a lockdown rule asynchronously. So it needs to wait for
// BlockedStatusCallback to be fired before checking the blocking status of incoming
// packets.
remoteUidCallback.expectBlockedStatusCallback(network, BLOCKED_REASON_LOCKDOWN_VPN);
if (SdkLevel.isAtLeastT()) {
// On T and above, lockdown rule drop packets not coming from lo regardless of the
// VPN connectivity.
checkBlockIncomingPacket(tunFd, remoteUdpFd, EXPECT_BLOCK);
}
// Start the VPN that has default routes. This VPN should have interface filtering rule
// for incoming packet and drop packets not coming from lo nor the VPN interface.
final String allowedApps =
mRemoteSocketFactoryClient.getPackageName() + "," + mPackageName;
startVpn(new String[]{"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
new String[]{"0.0.0.0/0", "::/0"}, allowedApps, "" /* disallowedApplications */,
null /* proxyInfo */, null /* underlyingNetworks */,
false /* isAlwaysMetered */);
checkBlockIncomingPacket(tunFd, remoteUdpFd, EXPECT_BLOCK);
}, /* cleanup */ () -> {
mCM.unregisterNetworkCallback(remoteUidCallback);
}, /* cleanup */ () -> {
Os.close(tunFd);
}, /* cleanup */ () -> {
Os.close(remoteUdpFd);
}, /* cleanup */ () -> {
runWithShellPermissionIdentity(() -> {
mCM.setRequireVpnForUids(false /* requireVpn */, lockdownRange);
}, NETWORK_SETTINGS);
});
}
private ByteBuffer buildIpv4UdpPacket(final Inet4Address dstAddr, final Inet4Address srcAddr,
final short dstPort, final short srcPort, final byte[] payload) throws IOException {
final ByteBuffer buffer = PacketBuilder.allocate(false /* hasEther */,
OsConstants.IPPROTO_IP, OsConstants.IPPROTO_UDP, payload.length);
final PacketBuilder packetBuilder = new PacketBuilder(buffer);
packetBuilder.writeIpv4Header(
(byte) 0 /* TOS */,
(short) 27149 /* ID */,
(short) 0x4000 /* flags=DF, offset=0 */,
(byte) 64 /* TTL */,
(byte) OsConstants.IPPROTO_UDP,
srcAddr,
dstAddr);
packetBuilder.writeUdpHeader(srcPort, dstPort);
buffer.put(payload);
return packetBuilder.finalizePacket();
}
private ByteBuffer buildIpv6UdpPacket(final Inet6Address dstAddr, final Inet6Address srcAddr,
final short dstPort, final short srcPort, final byte[] payload) throws IOException {
final ByteBuffer buffer = PacketBuilder.allocate(false /* hasEther */,
OsConstants.IPPROTO_IPV6, OsConstants.IPPROTO_UDP, payload.length);
final PacketBuilder packetBuilder = new PacketBuilder(buffer);
packetBuilder.writeIpv6Header(
0x60000000 /* version=6, traffic class=0, flow label=0 */,
(byte) OsConstants.IPPROTO_UDP,
(short) 64 /* hop limit */,
srcAddr,
dstAddr);
packetBuilder.writeUdpHeader(srcPort, dstPort);
buffer.put(payload);
return packetBuilder.finalizePacket();
}
private void checkBlockUdp(
final FileDescriptor srcTunFd,
final FileDescriptor dstUdpFd,
final boolean ipv6,
final boolean expectBlock) throws Exception {
final Random random = new Random();
final byte[] sendData = new byte[100];
random.nextBytes(sendData);
final short dstPort = (short) ((InetSocketAddress) Os.getsockname(dstUdpFd)).getPort();
ByteBuffer buf;
if (ipv6) {
buf = buildIpv6UdpPacket(
(Inet6Address) TEST_IP6_DST_ADDR.getAddress(),
(Inet6Address) TEST_IP6_SRC_ADDR.getAddress(),
dstPort, TEST_SRC_PORT, sendData);
} else {
buf = buildIpv4UdpPacket(
(Inet4Address) TEST_IP4_DST_ADDR.getAddress(),
(Inet4Address) TEST_IP4_SRC_ADDR.getAddress(),
dstPort, TEST_SRC_PORT, sendData);
}
Os.write(srcTunFd, buf);
final StructPollfd pollfd = new StructPollfd();
pollfd.events = (short) POLLIN;
pollfd.fd = dstUdpFd;
final int ret = Os.poll(new StructPollfd[]{pollfd}, SOCKET_TIMEOUT_MS);
if (expectBlock) {
assertEquals("Expect not to receive a packet but received a packet", 0, ret);
} else {
assertEquals("Expect to receive a packet but did not receive a packet", 1, ret);
final byte[] recvData = new byte[sendData.length];
final int readSize = Os.read(dstUdpFd, recvData, 0 /* byteOffset */, recvData.length);
assertEquals(recvData.length, readSize);
MoreAsserts.assertEquals(sendData, recvData);
}
}
private void checkBlockIncomingPacket(
final FileDescriptor srcTunFd,
final FileDescriptor dstUdpFd,
final boolean expectBlock) throws Exception {
checkBlockUdp(srcTunFd, dstUdpFd, false /* ipv6 */, expectBlock);
checkBlockUdp(srcTunFd, dstUdpFd, true /* ipv6 */, expectBlock);
}
private class DetailedBlockedStatusCallback extends TestableNetworkCallback {
public void expectAvailableCallbacks(Network network) {
super.expectAvailableCallbacks(network, false /* suspended */, true /* validated */,
BLOCKED_REASON_NONE, NETWORK_CALLBACK_TIMEOUT_MS);
}
public void expectBlockedStatusCallback(Network network, int blockedStatus) {
super.expectBlockedStatusCallback(blockedStatus, network, NETWORK_CALLBACK_TIMEOUT_MS);
}
public void onBlockedStatusChanged(Network network, int blockedReasons) {
getHistory().add(new CallbackEntry.BlockedStatusInt(network, blockedReasons));
}
}
}

View file

@ -17,16 +17,17 @@
package com.android.cts.net.hostside.app2;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.util.Log;
import com.android.cts.net.hostside.IRemoteSocketFactory;
import java.io.UncheckedIOException;
import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;
public class RemoteSocketFactoryService extends Service {
@ -54,6 +55,16 @@ public class RemoteSocketFactoryService extends Service {
public int getUid() {
return Process.myUid();
}
@Override
public ParcelFileDescriptor openDatagramSocketFd() {
try {
final DatagramSocket s = new DatagramSocket();
return ParcelFileDescriptor.fromDatagramSocket(s);
} catch (SocketException e) {
throw new UncheckedIOException(e);
}
}
};
@Override

View file

@ -116,4 +116,8 @@ public class HostsideVpnTests extends HostsideNetworkTestCase {
public void testInterleavedRoutes() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testInterleavedRoutes");
}
public void testBlockIncomingPackets() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBlockIncomingPackets");
}
}