diff --git a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java index 35d0f485e0..087dbdaec3 100644 --- a/tests/cts/net/src/android/net/cts/IpSecBaseTest.java +++ b/tests/cts/net/src/android/net/cts/IpSecBaseTest.java @@ -28,11 +28,12 @@ import android.system.OsConstants; import android.test.AndroidTestCase; import android.util.Log; +import androidx.test.InstrumentationRegistry; + import java.io.FileDescriptor; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; -import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -72,8 +73,14 @@ public class IpSecBaseTest extends AndroidTestCase { protected void setUp() throws Exception { super.setUp(); - mISM = (IpSecManager) getContext().getSystemService(Context.IPSEC_SERVICE); - mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE); + mISM = + (IpSecManager) + InstrumentationRegistry.getContext() + .getSystemService(Context.IPSEC_SERVICE); + mCM = + (ConnectivityManager) + InstrumentationRegistry.getContext() + .getSystemService(Context.CONNECTIVITY_SERVICE); } protected static byte[] getKey(int bitLength) { @@ -195,6 +202,17 @@ public class IpSecBaseTest extends AndroidTestCase { public static class JavaUdpSocket implements GenericUdpSocket { public final DatagramSocket mSocket; + public JavaUdpSocket(InetAddress localAddr, int port) { + try { + mSocket = new DatagramSocket(port, localAddr); + mSocket.setSoTimeout(SOCK_TIMEOUT); + } catch (SocketException e) { + // Fail loudly if we can't set up sockets properly. And without the timeout, we + // could easily end up in an endless wait. + throw new RuntimeException(e); + } + } + public JavaUdpSocket(InetAddress localAddr) { try { mSocket = new DatagramSocket(0, localAddr); @@ -425,26 +443,25 @@ public class IpSecBaseTest extends AndroidTestCase { } protected static IpSecTransform buildIpSecTransform( - Context mContext, + Context context, IpSecManager.SecurityParameterIndex spi, IpSecManager.UdpEncapsulationSocket encapSocket, InetAddress remoteAddr) throws Exception { - String localAddr = (remoteAddr instanceof Inet4Address) ? IPV4_LOOPBACK : IPV6_LOOPBACK; IpSecTransform.Builder builder = - new IpSecTransform.Builder(mContext) - .setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY)) - .setAuthentication( - new IpSecAlgorithm( - IpSecAlgorithm.AUTH_HMAC_SHA256, - AUTH_KEY, - AUTH_KEY.length * 4)); + new IpSecTransform.Builder(context) + .setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY)) + .setAuthentication( + new IpSecAlgorithm( + IpSecAlgorithm.AUTH_HMAC_SHA256, + AUTH_KEY, + AUTH_KEY.length * 4)); if (encapSocket != null) { builder.setIpv4Encapsulation(encapSocket, encapSocket.getPort()); } - return builder.buildTransportModeTransform(InetAddress.getByName(localAddr), spi); + return builder.buildTransportModeTransform(remoteAddr, spi); } private IpSecTransform buildDefaultTransform(InetAddress localAddr) throws Exception { diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java index c8c99f4a37..e8c0a7a4b2 100644 --- a/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java +++ b/tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java @@ -16,174 +16,728 @@ package android.net.cts; +import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS; +import static android.net.IpSecManager.UdpEncapsulationSocket; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; +import static android.net.NetworkCapabilities.TRANSPORT_TEST; +import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE; +import static android.net.cts.PacketUtils.AES_CBC_IV_LEN; +import static android.net.cts.PacketUtils.BytePayload; +import static android.net.cts.PacketUtils.EspHeader; +import static android.net.cts.PacketUtils.IP4_HDRLEN; +import static android.net.cts.PacketUtils.IP6_HDRLEN; +import static android.net.cts.PacketUtils.Ip4Header; +import static android.net.cts.PacketUtils.Ip6Header; +import static android.net.cts.PacketUtils.IpHeader; +import static android.net.cts.PacketUtils.UDP_HDRLEN; +import static android.net.cts.PacketUtils.UdpHeader; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; + +import static org.junit.Assert.assertArrayEquals; 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.app.AppOpsManager; +import android.content.Context; import android.content.pm.PackageManager; +import android.net.ConnectivityManager; import android.net.IpSecAlgorithm; import android.net.IpSecManager; import android.net.IpSecTransform; +import android.net.LinkAddress; import android.net.Network; +import android.net.NetworkRequest; +import android.net.TestNetworkInterface; +import android.net.TestNetworkManager; +import android.net.cts.PacketUtils.Payload; +import android.os.Binder; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; import com.android.compatibility.common.util.SystemUtil; +import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InterfaceAddress; import java.net.NetworkInterface; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) 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; + private static final InetAddress LOCAL_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.1"); + private static final InetAddress REMOTE_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.2"); + private static final InetAddress LOCAL_OUTER_6 = + InetAddress.parseNumericAddress("2001:db8:1::1"); + private static final InetAddress REMOTE_OUTER_6 = + InetAddress.parseNumericAddress("2001:db8:1::2"); - protected void setUp() throws Exception { + private static final InetAddress LOCAL_INNER_4 = + InetAddress.parseNumericAddress("198.51.100.1"); + private static final InetAddress REMOTE_INNER_4 = + InetAddress.parseNumericAddress("198.51.100.2"); + private static final InetAddress LOCAL_INNER_6 = + InetAddress.parseNumericAddress("2001:db8:2::1"); + private static final InetAddress REMOTE_INNER_6 = + InetAddress.parseNumericAddress("2001:db8:2::2"); + + private static final int IP4_PREFIX_LEN = 32; + private static final int IP6_PREFIX_LEN = 128; + + private static final int TIMEOUT_MS = 500; + + // Static state to reduce setup/teardown + private static ConnectivityManager sCM; + private static TestNetworkManager sTNM; + private static ParcelFileDescriptor sTunFd; + private static TestNetworkCallback sTunNetworkCallback; + private static Network sTunNetwork; + private static TunUtils sTunUtils; + + private static Context sContext = InstrumentationRegistry.getContext(); + private static IBinder sBinder = new Binder(); + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + InstrumentationRegistry.getInstrumentation() + .getUiAutomation() + .adoptShellPermissionIdentity(); + sCM = (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE); + sTNM = (TestNetworkManager) sContext.getSystemService(Context.TEST_NETWORK_SERVICE); + + // Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted, and + // a standard permission is insufficient. So we shell out the appop, to give us the + // right appop permissions. + setAppop(OP_MANAGE_IPSEC_TUNNELS, true); + + TestNetworkInterface testIntf = + sTNM.createTunInterface( + new LinkAddress[] { + new LinkAddress(LOCAL_OUTER_4, IP4_PREFIX_LEN), + new LinkAddress(LOCAL_OUTER_6, IP6_PREFIX_LEN) + }); + + sTunFd = testIntf.getFileDescriptor(); + sTunNetworkCallback = setupAndGetTestNetwork(testIntf.getInterfaceName()); + sTunNetwork = sTunNetworkCallback.getNetworkBlocking(); + + sTunUtils = new TunUtils(sTunFd); + } + + @Before + public void setUp() throws Exception { super.setUp(); + + // Set to true before every run; some tests flip this. + setAppop(OP_MANAGE_IPSEC_TUNNELS, true); + + // Clear sTunUtils state + sTunUtils.reset(); } - protected void tearDown() { - setAppop(false); + @AfterClass + public static void tearDownAfterClass() throws Exception { + setAppop(OP_MANAGE_IPSEC_TUNNELS, false); + + sCM.unregisterNetworkCallback(sTunNetworkCallback); + + sTNM.teardownTestNetwork(sTunNetwork); + sTunFd.close(); + + InstrumentationRegistry.getInstrumentation() + .getUiAutomation() + .dropShellPermissionIdentity(); } - private boolean hasTunnelsFeature() { - return getContext() - .getPackageManager() - .hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS); + private static boolean hasTunnelsFeature() { + return sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS); } - 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); + private static void setAppop(int appop, boolean allow) { + String opName = AppOpsManager.opToName(appop); + for (String pkg : new String[] {"com.android.shell", sContext.getPackageName()}) { + String cmd = + String.format( + "appops set %s %s %s", + pkg, // Package name + opName, // Appop + (allow ? "allow" : "deny")); // Action + SystemUtil.runShellCommand(cmd); + } } - public void testSecurityExceptionsCreateTunnelInterface() throws Exception { + private static TestNetworkCallback setupAndGetTestNetwork(String ifname) throws Exception { + // Build a network request + NetworkRequest nr = + new NetworkRequest.Builder() + .addTransportType(TRANSPORT_TEST) + .removeCapability(NET_CAPABILITY_TRUSTED) + .removeCapability(NET_CAPABILITY_NOT_VPN) + .setNetworkSpecifier(ifname) + .build(); + + TestNetworkCallback cb = new TestNetworkCallback(); + sCM.requestNetwork(nr, cb); + + // Setup the test network after network request is filed to prevent Network from being + // reaped due to no requests matching it. + sTNM.setupTestNetwork(ifname, sBinder); + + return cb; + } + + @Test + public void testSecurityExceptionCreateTunnelInterfaceWithoutAppop() throws Exception { if (!hasTunnelsFeature()) return; // Ensure we don't have the appop. Permission is not requested in the Manifest - setAppop(false); + setAppop(OP_MANAGE_IPSEC_TUNNELS, false); // Security exceptions are thrown regardless of IPv4/IPv6. Just test one try { - mISM.createIpSecTunnelInterface(OUTER_ADDR6, OUTER_ADDR6, mUnderlyingNetwork); + mISM.createIpSecTunnelInterface(LOCAL_INNER_6, REMOTE_INNER_6, sTunNetwork); fail("Did not throw SecurityException for Tunnel creation without appop"); } catch (SecurityException expected) { } } - public void testSecurityExceptionsBuildTunnelTransform() throws Exception { + @Test + public void testSecurityExceptionBuildTunnelTransformWithoutAppop() throws Exception { if (!hasTunnelsFeature()) return; // Ensure we don't have the appop. Permission is not requested in the Manifest - setAppop(false); + setAppop(OP_MANAGE_IPSEC_TUNNELS, false); // Security exceptions are thrown regardless of IPv4/IPv6. Just test one try (IpSecManager.SecurityParameterIndex spi = - mISM.allocateSecurityParameterIndex(OUTER_ADDR4); + mISM.allocateSecurityParameterIndex(LOCAL_INNER_4); IpSecTransform transform = - new IpSecTransform.Builder(mContext) - .buildTunnelModeTransform(OUTER_ADDR4, spi)) { + new IpSecTransform.Builder(sContext) + .buildTunnelModeTransform(REMOTE_INNER_4, spi)) { fail("Did not throw SecurityException for Transform creation without appop"); } catch (SecurityException expected) { } } - private void checkTunnel(InetAddress inner, InetAddress outer, boolean useEncap) + /* Test runnables for callbacks after IPsec tunnels are set up. */ + private interface TestRunnable { + void run(Network ipsecNetwork) throws Exception; + } + + private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback { + private final CompletableFuture futureNetwork = new CompletableFuture<>(); + + @Override + public void onAvailable(Network network) { + futureNetwork.complete(network); + } + + public Network getNetworkBlocking() throws Exception { + return futureNetwork.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); + } + } + + private int getPacketSize( + int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) { + int expectedPacketSize = TEST_DATA.length + UDP_HDRLEN; + + // Inner Transport mode packet size + if (transportInTunnelMode) { + expectedPacketSize = + PacketUtils.calculateEspPacketSize( + expectedPacketSize, + AES_CBC_IV_LEN, + AES_CBC_BLK_SIZE, + AUTH_KEY.length * 4); + } + + // Inner IP Header + expectedPacketSize += innerFamily == AF_INET ? IP4_HDRLEN : IP6_HDRLEN; + + // Tunnel mode transform size + expectedPacketSize = + PacketUtils.calculateEspPacketSize( + expectedPacketSize, AES_CBC_IV_LEN, AES_CBC_BLK_SIZE, AUTH_KEY.length * 4); + + // UDP encap size + expectedPacketSize += useEncap ? UDP_HDRLEN : 0; + + // Outer IP Header + expectedPacketSize += outerFamily == AF_INET ? IP4_HDRLEN : IP6_HDRLEN; + + return expectedPacketSize; + } + + private interface TestRunnableFactory { + TestRunnable getTestRunnable( + boolean transportInTunnelMode, + int spi, + InetAddress localInner, + InetAddress remoteInner, + InetAddress localOuter, + InetAddress remoteOuter, + IpSecTransform inTransportTransform, + IpSecTransform outTransportTransform, + int encapPort, + int expectedPacketSize) + throws Exception; + } + + private class OutputTestRunnableFactory implements TestRunnableFactory { + public TestRunnable getTestRunnable( + boolean transportInTunnelMode, + int spi, + InetAddress localInner, + InetAddress remoteInner, + InetAddress localOuter, + InetAddress remoteOuter, + IpSecTransform inTransportTransform, + IpSecTransform outTransportTransform, + int encapPort, + int expectedPacketSize) { + return new TestRunnable() { + @Override + public void run(Network ipsecNetwork) throws Exception { + // Build a socket and send traffic + JavaUdpSocket socket = new JavaUdpSocket(localInner); + ipsecNetwork.bindSocket(socket.mSocket); + + // For Transport-In-Tunnel mode, apply transform to socket + if (transportInTunnelMode) { + mISM.applyTransportModeTransform( + socket.mSocket, IpSecManager.DIRECTION_IN, inTransportTransform); + mISM.applyTransportModeTransform( + socket.mSocket, IpSecManager.DIRECTION_OUT, outTransportTransform); + } + + socket.sendTo(TEST_DATA, remoteInner, socket.getPort()); + + // Verify that an encrypted packet is sent. As of right now, checking encrypted + // body is not possible, due to our not knowing some of the fields of the + // inner IP header (flow label, flags, etc) + sTunUtils.awaitEspPacketNoPlaintext( + spi, TEST_DATA, encapPort != 0, expectedPacketSize); + + socket.close(); + } + }; + } + } + + private class InputPacketGeneratorTestRunnableFactory implements TestRunnableFactory { + public TestRunnable getTestRunnable( + boolean transportInTunnelMode, + int spi, + InetAddress localInner, + InetAddress remoteInner, + InetAddress localOuter, + InetAddress remoteOuter, + IpSecTransform inTransportTransform, + IpSecTransform outTransportTransform, + int encapPort, + int expectedPacketSize) + throws Exception { + return new TestRunnable() { + @Override + public void run(Network ipsecNetwork) throws Exception { + // Build a socket and receive traffic + JavaUdpSocket socket = new JavaUdpSocket(localInner); + // JavaUdpSocket socket = new JavaUdpSocket(localInner, socketPort.get()); + ipsecNetwork.bindSocket(socket.mSocket); + + // For Transport-In-Tunnel mode, apply transform to socket + if (transportInTunnelMode) { + mISM.applyTransportModeTransform( + socket.mSocket, IpSecManager.DIRECTION_IN, outTransportTransform); + mISM.applyTransportModeTransform( + socket.mSocket, IpSecManager.DIRECTION_OUT, inTransportTransform); + } + + byte[] pkt; + if (transportInTunnelMode) { + pkt = + getTransportInTunnelModePacket( + spi, + spi, + remoteInner, + localInner, + remoteOuter, + localOuter, + socket.getPort(), + encapPort); + } else { + pkt = + getTunnelModePacket( + spi, + remoteInner, + localInner, + remoteOuter, + localOuter, + socket.getPort(), + encapPort); + } + sTunUtils.injectPacket(pkt); + + // Receive packet from socket, and validate + receiveAndValidatePacket(socket); + + socket.close(); + } + }; + } + } + + private void checkTunnelOutput( + int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) + throws Exception { + checkTunnel( + innerFamily, + outerFamily, + useEncap, + transportInTunnelMode, + new OutputTestRunnableFactory()); + } + + private void checkTunnelInput( + int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) + throws Exception { + checkTunnel( + innerFamily, + outerFamily, + useEncap, + transportInTunnelMode, + new InputPacketGeneratorTestRunnableFactory()); + } + + public void checkTunnel( + int innerFamily, + int outerFamily, + boolean useEncap, + boolean transportInTunnelMode, + TestRunnableFactory factory) throws Exception { if (!hasTunnelsFeature()) return; - setAppop(true); - int innerPrefixLen = inner instanceof Inet6Address ? IP6_PREFIX_LEN : IP4_PREFIX_LEN; + InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6; + InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6; - try (IpSecManager.SecurityParameterIndex spi = mISM.allocateSecurityParameterIndex(outer); + InetAddress localOuter = outerFamily == AF_INET ? LOCAL_OUTER_4 : LOCAL_OUTER_6; + InetAddress remoteOuter = outerFamily == AF_INET ? REMOTE_OUTER_4 : REMOTE_OUTER_6; + + // Preselect both SPI and encap port, to be used for both inbound and outbound tunnels. + // Re-uses the same SPI to ensure that even in cases of symmetric SPIs shared across tunnel + // and transport mode, packets are encrypted/decrypted properly based on the src/dst. + int spi = getRandomSpi(localOuter, remoteOuter); + int expectedPacketSize = + getPacketSize(innerFamily, outerFamily, useEncap, transportInTunnelMode); + + try (IpSecManager.SecurityParameterIndex inTransportSpi = + mISM.allocateSecurityParameterIndex(localInner, spi); + IpSecManager.SecurityParameterIndex outTransportSpi = + mISM.allocateSecurityParameterIndex(remoteInner, spi); + IpSecTransform inTransportTransform = + buildIpSecTransform(sContext, inTransportSpi, null, remoteInner); + IpSecTransform outTransportTransform = + buildIpSecTransform(sContext, outTransportSpi, null, localInner); + UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) { + + buildTunnelAndNetwork( + localInner, + remoteInner, + localOuter, + remoteOuter, + spi, + useEncap ? encapSocket : null, + factory.getTestRunnable( + transportInTunnelMode, + spi, + localInner, + remoteInner, + localOuter, + remoteOuter, + inTransportTransform, + outTransportTransform, + useEncap ? encapSocket.getPort() : 0, + expectedPacketSize)); + } + } + + private void buildTunnelAndNetwork( + InetAddress localInner, + InetAddress remoteInner, + InetAddress localOuter, + InetAddress remoteOuter, + int spi, + UdpEncapsulationSocket encapSocket, + TestRunnable test) + throws Exception { + int innerPrefixLen = localInner instanceof Inet6Address ? IP6_PREFIX_LEN : IP4_PREFIX_LEN; + TestNetworkCallback testNetworkCb = null; + + try (IpSecManager.SecurityParameterIndex inSpi = + mISM.allocateSecurityParameterIndex(localOuter, spi); + IpSecManager.SecurityParameterIndex outSpi = + mISM.allocateSecurityParameterIndex(remoteOuter, spi); IpSecManager.IpSecTunnelInterface tunnelIntf = - mISM.createIpSecTunnelInterface(outer, outer, mCM.getActiveNetwork()); - IpSecManager.UdpEncapsulationSocket encapSocket = - mISM.openUdpEncapsulationSocket()) { + mISM.createIpSecTunnelInterface(localOuter, remoteOuter, sTunNetwork)) { + // Build the test network + tunnelIntf.addAddress(localInner, innerPrefixLen); + testNetworkCb = setupAndGetTestNetwork(tunnelIntf.getInterfaceName()); + Network testNetwork = testNetworkCb.getNetworkBlocking(); - IpSecTransform.Builder transformBuilder = new IpSecTransform.Builder(mContext); + // Check interface was created + NetworkInterface netIntf = NetworkInterface.getByName(tunnelIntf.getInterfaceName()); + assertNotNull(netIntf); + + // Check addresses + List intfAddrs = netIntf.getInterfaceAddresses(); + assertEquals(1, intfAddrs.size()); + assertEquals(localInner, intfAddrs.get(0).getAddress()); + assertEquals(innerPrefixLen, intfAddrs.get(0).getNetworkPrefixLength()); + + // Configure Transform parameters + IpSecTransform.Builder transformBuilder = new IpSecTransform.Builder(sContext); 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) { + if (encapSocket != null) { 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); + // Apply transform and check that traffic is properly encrypted + try (IpSecTransform inTransform = + transformBuilder.buildTunnelModeTransform(remoteOuter, inSpi); + IpSecTransform outTransform = + transformBuilder.buildTunnelModeTransform(localOuter, outSpi)) { + mISM.applyTunnelModeTransform(tunnelIntf, IpSecManager.DIRECTION_IN, inTransform); + mISM.applyTunnelModeTransform(tunnelIntf, IpSecManager.DIRECTION_OUT, outTransform); - // TODO: Test to ensure that send/receive works with these transforms. + test.run(testNetwork); } - // 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); - } + // Teardown the test network + sTNM.teardownTestNetwork(testNetwork); // Remove addresses and check - tunnelIntf.removeAddress(inner, innerPrefixLen); + tunnelIntf.removeAddress(localInner, innerPrefixLen); + netIntf = NetworkInterface.getByName(tunnelIntf.getInterfaceName()); assertTrue(netIntf.getInterfaceAddresses().isEmpty()); // Check interface was cleaned up tunnelIntf.close(); netIntf = NetworkInterface.getByName(tunnelIntf.getInterfaceName()); assertNull(netIntf); + } finally { + if (testNetworkCb != null) { + sCM.unregisterNetworkCallback(testNetworkCb); + } } } - /* - * Create, add and remove addresses, then teardown tunnel - */ + private static void receiveAndValidatePacket(JavaUdpSocket socket) throws Exception { + byte[] socketResponseBytes = socket.receive(); + assertArrayEquals(TEST_DATA, socketResponseBytes); + } + + private int getRandomSpi(InetAddress localOuter, InetAddress remoteOuter) throws Exception { + // Try to allocate both in and out SPIs using the same requested SPI value. + try (IpSecManager.SecurityParameterIndex inSpi = + mISM.allocateSecurityParameterIndex(localOuter); + IpSecManager.SecurityParameterIndex outSpi = + mISM.allocateSecurityParameterIndex(remoteOuter, inSpi.getSpi()); ) { + return inSpi.getSpi(); + } + } + + private IpHeader getIpHeader(int protocol, InetAddress src, InetAddress dst, Payload payload) { + if ((src instanceof Inet6Address) != (dst instanceof Inet6Address)) { + throw new IllegalArgumentException("Invalid src/dst address combination"); + } + + if (src instanceof Inet6Address) { + return new Ip6Header(protocol, (Inet6Address) src, (Inet6Address) dst, payload); + } else { + return new Ip4Header(protocol, (Inet4Address) src, (Inet4Address) dst, payload); + } + } + + private EspHeader buildTransportModeEspPacket( + int spi, InetAddress src, InetAddress dst, int port, Payload payload) throws Exception { + IpHeader preEspIpHeader = getIpHeader(payload.getProtocolId(), src, dst, payload); + + return new EspHeader( + payload.getProtocolId(), + spi, + 1, // sequence number + CRYPT_KEY, // Same key for auth and crypt + payload.getPacketBytes(preEspIpHeader)); + } + + private EspHeader buildTunnelModeEspPacket( + int spi, + InetAddress srcInner, + InetAddress dstInner, + InetAddress srcOuter, + InetAddress dstOuter, + int port, + int encapPort, + Payload payload) + throws Exception { + IpHeader innerIp = getIpHeader(payload.getProtocolId(), srcInner, dstInner, payload); + return new EspHeader( + innerIp.getProtocolId(), + spi, + 1, // sequence number + CRYPT_KEY, // Same key for auth and crypt + innerIp.getPacketBytes()); + } + + private IpHeader maybeEncapPacket( + InetAddress src, InetAddress dst, int encapPort, EspHeader espPayload) + throws Exception { + + Payload payload = espPayload; + if (encapPort != 0) { + payload = new UdpHeader(encapPort, encapPort, espPayload); + } + + return getIpHeader(payload.getProtocolId(), src, dst, payload); + } + + private byte[] getTunnelModePacket( + int spi, + InetAddress srcInner, + InetAddress dstInner, + InetAddress srcOuter, + InetAddress dstOuter, + int port, + int encapPort) + throws Exception { + UdpHeader udp = new UdpHeader(port, port, new BytePayload(TEST_DATA)); + + EspHeader espPayload = + buildTunnelModeEspPacket( + spi, srcInner, dstInner, srcOuter, dstOuter, port, encapPort, udp); + return maybeEncapPacket(srcOuter, dstOuter, encapPort, espPayload).getPacketBytes(); + } + + private byte[] getTransportInTunnelModePacket( + int spiInner, + int spiOuter, + InetAddress srcInner, + InetAddress dstInner, + InetAddress srcOuter, + InetAddress dstOuter, + int port, + int encapPort) + throws Exception { + UdpHeader udp = new UdpHeader(port, port, new BytePayload(TEST_DATA)); + + EspHeader espPayload = buildTransportModeEspPacket(spiInner, srcInner, dstInner, port, udp); + espPayload = + buildTunnelModeEspPacket( + spiOuter, + srcInner, + dstInner, + srcOuter, + dstOuter, + port, + encapPort, + espPayload); + return maybeEncapPacket(srcOuter, dstOuter, encapPort, espPayload).getPacketBytes(); + } + + // Transport-in-Tunnel mode tests + @Test + public void testTransportInTunnelModeV4InV4() throws Exception { + checkTunnelOutput(AF_INET, AF_INET, false, true); + checkTunnelInput(AF_INET, AF_INET, false, true); + } + + @Test + public void testTransportInTunnelModeV4InV4UdpEncap() throws Exception { + checkTunnelOutput(AF_INET, AF_INET, true, true); + checkTunnelInput(AF_INET, AF_INET, true, true); + } + + @Test + public void testTransportInTunnelModeV4InV6() throws Exception { + checkTunnelOutput(AF_INET, AF_INET6, false, true); + checkTunnelInput(AF_INET, AF_INET6, false, true); + } + + @Test + public void testTransportInTunnelModeV6InV4() throws Exception { + checkTunnelOutput(AF_INET6, AF_INET, false, true); + checkTunnelInput(AF_INET6, AF_INET, false, true); + } + + @Test + public void testTransportInTunnelModeV6InV4UdpEncap() throws Exception { + checkTunnelOutput(AF_INET6, AF_INET, true, true); + checkTunnelInput(AF_INET6, AF_INET, true, true); + } + + @Test + public void testTransportInTunnelModeV6InV6() throws Exception { + checkTunnelOutput(AF_INET, AF_INET6, false, true); + checkTunnelInput(AF_INET, AF_INET6, false, true); + } + + // Tunnel mode tests + @Test public void testTunnelV4InV4() throws Exception { - checkTunnel(INNER_ADDR4, OUTER_ADDR4, false); + checkTunnelOutput(AF_INET, AF_INET, false, false); + checkTunnelInput(AF_INET, AF_INET, false, false); } + @Test public void testTunnelV4InV4UdpEncap() throws Exception { - checkTunnel(INNER_ADDR4, OUTER_ADDR4, true); + checkTunnelOutput(AF_INET, AF_INET, true, false); + checkTunnelInput(AF_INET, AF_INET, true, false); } + @Test public void testTunnelV4InV6() throws Exception { - checkTunnel(INNER_ADDR4, OUTER_ADDR6, false); + checkTunnelOutput(AF_INET, AF_INET6, false, false); + checkTunnelInput(AF_INET, AF_INET6, false, false); } + @Test public void testTunnelV6InV4() throws Exception { - checkTunnel(INNER_ADDR6, OUTER_ADDR4, false); + checkTunnelOutput(AF_INET6, AF_INET, false, false); + checkTunnelInput(AF_INET6, AF_INET, false, false); } + @Test public void testTunnelV6InV4UdpEncap() throws Exception { - checkTunnel(INNER_ADDR6, OUTER_ADDR4, true); + checkTunnelOutput(AF_INET6, AF_INET, true, false); + checkTunnelInput(AF_INET6, AF_INET, true, false); } + @Test public void testTunnelV6InV6() throws Exception { - checkTunnel(INNER_ADDR6, OUTER_ADDR6, false); + checkTunnelOutput(AF_INET6, AF_INET6, false, false); + checkTunnelInput(AF_INET6, AF_INET6, false, false); } } diff --git a/tests/cts/net/src/android/net/cts/TunUtils.java b/tests/cts/net/src/android/net/cts/TunUtils.java index 4d5533fc62..a0307137a5 100644 --- a/tests/cts/net/src/android/net/cts/TunUtils.java +++ b/tests/cts/net/src/android/net/cts/TunUtils.java @@ -247,4 +247,11 @@ public class TunUtils { out.write(pkt); out.flush(); } + + /** Resets the intercepted packets. */ + public void reset() throws IOException { + synchronized (mPackets) { + mPackets.clear(); + } + } }