| /* |
| * 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 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.fail; |
| |
| 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.Build; |
| import android.os.IBinder; |
| import android.os.ParcelFileDescriptor; |
| import android.os.SystemProperties; |
| import android.platform.test.annotations.AppModeFull; |
| |
| 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.NetworkInterface; |
| 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) |
| @AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps") |
| public class IpSecManagerTunnelTest extends IpSecBaseTest { |
| private static final String TAG = IpSecManagerTunnelTest.class.getSimpleName(); |
| |
| 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"); |
| |
| 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 testIface = |
| sTNM.createTunInterface( |
| new LinkAddress[] { |
| new LinkAddress(LOCAL_OUTER_4, IP4_PREFIX_LEN), |
| new LinkAddress(LOCAL_OUTER_6, IP6_PREFIX_LEN) |
| }); |
| |
| sTunFd = testIface.getFileDescriptor(); |
| sTunNetworkCallback = setupAndGetTestNetwork(testIface.getInterfaceName()); |
| sTunNetwork = sTunNetworkCallback.getNetworkBlocking(); |
| |
| sTunUtils = new TunUtils(sTunFd); |
| } |
| |
| @Before |
| @Override |
| 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(); |
| } |
| |
| @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 static boolean hasTunnelsFeature() { |
| return sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS) |
| || SystemProperties.getInt("ro.product.first_api_level", 0) |
| >= Build.VERSION_CODES.Q; |
| } |
| |
| 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); |
| } |
| } |
| |
| 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(OP_MANAGE_IPSEC_TUNNELS, false); |
| |
| // Security exceptions are thrown regardless of IPv4/IPv6. Just test one |
| try { |
| mISM.createIpSecTunnelInterface(LOCAL_INNER_6, REMOTE_INNER_6, sTunNetwork); |
| fail("Did not throw SecurityException for Tunnel creation without appop"); |
| } catch (SecurityException expected) { |
| } |
| } |
| |
| @Test |
| public void testSecurityExceptionBuildTunnelTransformWithoutAppop() throws Exception { |
| if (!hasTunnelsFeature()) return; |
| |
| // Ensure we don't have the appop. Permission is not requested in the Manifest |
| setAppop(OP_MANAGE_IPSEC_TUNNELS, false); |
| |
| // Security exceptions are thrown regardless of IPv4/IPv6. Just test one |
| try (IpSecManager.SecurityParameterIndex spi = |
| mISM.allocateSecurityParameterIndex(LOCAL_INNER_4); |
| IpSecTransform transform = |
| new IpSecTransform.Builder(sContext) |
| .buildTunnelModeTransform(REMOTE_INNER_4, spi)) { |
| fail("Did not throw SecurityException for Transform creation without appop"); |
| } catch (SecurityException expected) { |
| } |
| } |
| |
| /* Test runnables for callbacks after IPsec tunnels are set up. */ |
| private abstract class IpSecTunnelTestRunnable { |
| /** |
| * Runs the test code, and returns the inner socket port, if any. |
| * |
| * @param ipsecNetwork The IPsec Interface based Network for binding sockets on |
| * @return the integer port of the inner socket if outbound, or 0 if inbound |
| * IpSecTunnelTestRunnable |
| * @throws Exception if any part of the test failed. |
| */ |
| public abstract int run(Network ipsecNetwork) throws Exception; |
| } |
| |
| private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback { |
| private final CompletableFuture<Network> 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 IpSecTunnelTestRunnableFactory { |
| IpSecTunnelTestRunnable getIpSecTunnelTestRunnable( |
| boolean transportInTunnelMode, |
| int spi, |
| InetAddress localInner, |
| InetAddress remoteInner, |
| InetAddress localOuter, |
| InetAddress remoteOuter, |
| IpSecTransform inTransportTransform, |
| IpSecTransform outTransportTransform, |
| int encapPort, |
| int innerSocketPort, |
| int expectedPacketSize) |
| throws Exception; |
| } |
| |
| private class OutputIpSecTunnelTestRunnableFactory implements IpSecTunnelTestRunnableFactory { |
| public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable( |
| boolean transportInTunnelMode, |
| int spi, |
| InetAddress localInner, |
| InetAddress remoteInner, |
| InetAddress localOuter, |
| InetAddress remoteOuter, |
| IpSecTransform inTransportTransform, |
| IpSecTransform outTransportTransform, |
| int encapPort, |
| int unusedInnerSocketPort, |
| int expectedPacketSize) { |
| return new IpSecTunnelTestRunnable() { |
| @Override |
| public int run(Network ipsecNetwork) throws Exception { |
| // Build a socket and send traffic |
| JavaUdpSocket socket = new JavaUdpSocket(localInner); |
| ipsecNetwork.bindSocket(socket.mSocket); |
| int innerSocketPort = socket.getPort(); |
| |
| // 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 the test 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(); |
| |
| return innerSocketPort; |
| } |
| }; |
| } |
| } |
| |
| private class InputReflectedIpSecTunnelTestRunnableFactory |
| implements IpSecTunnelTestRunnableFactory { |
| public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable( |
| boolean transportInTunnelMode, |
| int spi, |
| InetAddress localInner, |
| InetAddress remoteInner, |
| InetAddress localOuter, |
| InetAddress remoteOuter, |
| IpSecTransform inTransportTransform, |
| IpSecTransform outTransportTransform, |
| int encapPort, |
| int innerSocketPort, |
| int expectedPacketSize) |
| throws Exception { |
| return new IpSecTunnelTestRunnable() { |
| @Override |
| public int run(Network ipsecNetwork) throws Exception { |
| // Build a socket and receive traffic |
| JavaUdpSocket socket = new JavaUdpSocket(localInner, innerSocketPort); |
| 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); |
| } |
| |
| sTunUtils.reflectPackets(); |
| |
| // Receive packet from socket, and validate that the payload is correct |
| receiveAndValidatePacket(socket); |
| |
| socket.close(); |
| |
| return 0; |
| } |
| }; |
| } |
| } |
| |
| private class InputPacketGeneratorIpSecTunnelTestRunnableFactory |
| implements IpSecTunnelTestRunnableFactory { |
| public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable( |
| boolean transportInTunnelMode, |
| int spi, |
| InetAddress localInner, |
| InetAddress remoteInner, |
| InetAddress localOuter, |
| InetAddress remoteOuter, |
| IpSecTransform inTransportTransform, |
| IpSecTransform outTransportTransform, |
| int encapPort, |
| int innerSocketPort, |
| int expectedPacketSize) |
| throws Exception { |
| return new IpSecTunnelTestRunnable() { |
| @Override |
| public int run(Network ipsecNetwork) throws Exception { |
| // Build a socket and receive 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, 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(); |
| |
| return 0; |
| } |
| }; |
| } |
| } |
| |
| private void checkTunnelOutput( |
| int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) |
| throws Exception { |
| checkTunnel( |
| innerFamily, |
| outerFamily, |
| useEncap, |
| transportInTunnelMode, |
| new OutputIpSecTunnelTestRunnableFactory()); |
| } |
| |
| private void checkTunnelInput( |
| int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) |
| throws Exception { |
| checkTunnel( |
| innerFamily, |
| outerFamily, |
| useEncap, |
| transportInTunnelMode, |
| new InputPacketGeneratorIpSecTunnelTestRunnableFactory()); |
| } |
| |
| /** |
| * Validates that the kernel can talk to itself. |
| * |
| * <p>This test takes an outbound IPsec packet, reflects it (by flipping IP src/dst), and |
| * injects it back into the TUN. This test then verifies that a packet with the correct payload |
| * is found on the specified socket/port. |
| */ |
| public void checkTunnelReflected( |
| int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) |
| throws Exception { |
| if (!hasTunnelsFeature()) return; |
| |
| InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6; |
| InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6; |
| |
| 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. |
| 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()) { |
| |
| // Run output direction tests |
| IpSecTunnelTestRunnable outputIpSecTunnelTestRunnable = |
| new OutputIpSecTunnelTestRunnableFactory() |
| .getIpSecTunnelTestRunnable( |
| transportInTunnelMode, |
| spi, |
| localInner, |
| remoteInner, |
| localOuter, |
| remoteOuter, |
| inTransportTransform, |
| outTransportTransform, |
| useEncap ? encapSocket.getPort() : 0, |
| 0, |
| expectedPacketSize); |
| int innerSocketPort = |
| buildTunnelNetworkAndRunTests( |
| localInner, |
| remoteInner, |
| localOuter, |
| remoteOuter, |
| spi, |
| useEncap ? encapSocket : null, |
| outputIpSecTunnelTestRunnable); |
| |
| // Input direction tests, with matching inner socket ports. |
| IpSecTunnelTestRunnable inputIpSecTunnelTestRunnable = |
| new InputReflectedIpSecTunnelTestRunnableFactory() |
| .getIpSecTunnelTestRunnable( |
| transportInTunnelMode, |
| spi, |
| remoteInner, |
| localInner, |
| localOuter, |
| remoteOuter, |
| inTransportTransform, |
| outTransportTransform, |
| useEncap ? encapSocket.getPort() : 0, |
| innerSocketPort, |
| expectedPacketSize); |
| buildTunnelNetworkAndRunTests( |
| remoteInner, |
| localInner, |
| localOuter, |
| remoteOuter, |
| spi, |
| useEncap ? encapSocket : null, |
| inputIpSecTunnelTestRunnable); |
| } |
| } |
| |
| public void checkTunnel( |
| int innerFamily, |
| int outerFamily, |
| boolean useEncap, |
| boolean transportInTunnelMode, |
| IpSecTunnelTestRunnableFactory factory) |
| throws Exception { |
| if (!hasTunnelsFeature()) return; |
| |
| InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6; |
| InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6; |
| |
| 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()) { |
| |
| buildTunnelNetworkAndRunTests( |
| localInner, |
| remoteInner, |
| localOuter, |
| remoteOuter, |
| spi, |
| useEncap ? encapSocket : null, |
| factory.getIpSecTunnelTestRunnable( |
| transportInTunnelMode, |
| spi, |
| localInner, |
| remoteInner, |
| localOuter, |
| remoteOuter, |
| inTransportTransform, |
| outTransportTransform, |
| useEncap ? encapSocket.getPort() : 0, |
| 0, |
| expectedPacketSize)); |
| } |
| } |
| |
| private int buildTunnelNetworkAndRunTests( |
| InetAddress localInner, |
| InetAddress remoteInner, |
| InetAddress localOuter, |
| InetAddress remoteOuter, |
| int spi, |
| UdpEncapsulationSocket encapSocket, |
| IpSecTunnelTestRunnable test) |
| throws Exception { |
| int innerPrefixLen = localInner instanceof Inet6Address ? IP6_PREFIX_LEN : IP4_PREFIX_LEN; |
| TestNetworkCallback testNetworkCb = null; |
| int innerSocketPort; |
| |
| try (IpSecManager.SecurityParameterIndex inSpi = |
| mISM.allocateSecurityParameterIndex(localOuter, spi); |
| IpSecManager.SecurityParameterIndex outSpi = |
| mISM.allocateSecurityParameterIndex(remoteOuter, spi); |
| IpSecManager.IpSecTunnelInterface tunnelIface = |
| mISM.createIpSecTunnelInterface(localOuter, remoteOuter, sTunNetwork)) { |
| // Build the test network |
| tunnelIface.addAddress(localInner, innerPrefixLen); |
| testNetworkCb = setupAndGetTestNetwork(tunnelIface.getInterfaceName()); |
| Network testNetwork = testNetworkCb.getNetworkBlocking(); |
| |
| // Check interface was created |
| assertNotNull(NetworkInterface.getByName(tunnelIface.getInterfaceName())); |
| |
| // Verify address was added |
| final NetworkInterface netIface = NetworkInterface.getByInetAddress(localInner); |
| assertNotNull(netIface); |
| assertEquals(tunnelIface.getInterfaceName(), netIface.getDisplayName()); |
| |
| // 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 (encapSocket != null) { |
| transformBuilder.setIpv4Encapsulation(encapSocket, encapSocket.getPort()); |
| } |
| |
| // Apply transform and check that traffic is properly encrypted |
| try (IpSecTransform inTransform = |
| transformBuilder.buildTunnelModeTransform(remoteOuter, inSpi); |
| IpSecTransform outTransform = |
| transformBuilder.buildTunnelModeTransform(localOuter, outSpi)) { |
| mISM.applyTunnelModeTransform(tunnelIface, IpSecManager.DIRECTION_IN, inTransform); |
| mISM.applyTunnelModeTransform( |
| tunnelIface, IpSecManager.DIRECTION_OUT, outTransform); |
| |
| innerSocketPort = test.run(testNetwork); |
| } |
| |
| // Teardown the test network |
| sTNM.teardownTestNetwork(testNetwork); |
| |
| // Remove addresses and check that interface is still present, but fails lookup-by-addr |
| tunnelIface.removeAddress(localInner, innerPrefixLen); |
| assertNotNull(NetworkInterface.getByName(tunnelIface.getInterfaceName())); |
| assertNull(NetworkInterface.getByInetAddress(localInner)); |
| |
| // Check interface was cleaned up |
| tunnelIface.close(); |
| assertNull(NetworkInterface.getByName(tunnelIface.getInterfaceName())); |
| } finally { |
| if (testNetworkCb != null) { |
| sCM.unregisterNetworkCallback(testNetworkCb); |
| } |
| } |
| |
| return innerSocketPort; |
| } |
| |
| 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 testTransportInTunnelModeV4InV4Reflected() throws Exception { |
| checkTunnelReflected(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 testTransportInTunnelModeV4InV4UdpEncapReflected() throws Exception { |
| checkTunnelReflected(AF_INET, AF_INET, false, true); |
| } |
| |
| @Test |
| public void testTransportInTunnelModeV4InV6() throws Exception { |
| checkTunnelOutput(AF_INET, AF_INET6, false, true); |
| checkTunnelInput(AF_INET, AF_INET6, false, true); |
| } |
| |
| @Test |
| public void testTransportInTunnelModeV4InV6Reflected() throws Exception { |
| checkTunnelReflected(AF_INET, AF_INET, 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 testTransportInTunnelModeV6InV4Reflected() throws Exception { |
| checkTunnelReflected(AF_INET, 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 testTransportInTunnelModeV6InV4UdpEncapReflected() throws Exception { |
| checkTunnelReflected(AF_INET, AF_INET, false, true); |
| } |
| |
| @Test |
| public void testTransportInTunnelModeV6InV6() throws Exception { |
| checkTunnelOutput(AF_INET, AF_INET6, false, true); |
| checkTunnelInput(AF_INET, AF_INET6, false, true); |
| } |
| |
| @Test |
| public void testTransportInTunnelModeV6InV6Reflected() throws Exception { |
| checkTunnelReflected(AF_INET, AF_INET, false, true); |
| } |
| |
| // Tunnel mode tests |
| @Test |
| public void testTunnelV4InV4() throws Exception { |
| checkTunnelOutput(AF_INET, AF_INET, false, false); |
| checkTunnelInput(AF_INET, AF_INET, false, false); |
| } |
| |
| @Test |
| public void testTunnelV4InV4Reflected() throws Exception { |
| checkTunnelReflected(AF_INET, AF_INET, false, false); |
| } |
| |
| @Test |
| public void testTunnelV4InV4UdpEncap() throws Exception { |
| checkTunnelOutput(AF_INET, AF_INET, true, false); |
| checkTunnelInput(AF_INET, AF_INET, true, false); |
| } |
| |
| @Test |
| public void testTunnelV4InV4UdpEncapReflected() throws Exception { |
| checkTunnelReflected(AF_INET, AF_INET, true, false); |
| } |
| |
| @Test |
| public void testTunnelV4InV6() throws Exception { |
| checkTunnelOutput(AF_INET, AF_INET6, false, false); |
| checkTunnelInput(AF_INET, AF_INET6, false, false); |
| } |
| |
| @Test |
| public void testTunnelV4InV6Reflected() throws Exception { |
| checkTunnelReflected(AF_INET, AF_INET6, false, false); |
| } |
| |
| @Test |
| public void testTunnelV6InV4() throws Exception { |
| checkTunnelOutput(AF_INET6, AF_INET, false, false); |
| checkTunnelInput(AF_INET6, AF_INET, false, false); |
| } |
| |
| @Test |
| public void testTunnelV6InV4Reflected() throws Exception { |
| checkTunnelReflected(AF_INET6, AF_INET, false, false); |
| } |
| |
| @Test |
| public void testTunnelV6InV4UdpEncap() throws Exception { |
| checkTunnelOutput(AF_INET6, AF_INET, true, false); |
| checkTunnelInput(AF_INET6, AF_INET, true, false); |
| } |
| |
| @Test |
| public void testTunnelV6InV4UdpEncapReflected() throws Exception { |
| checkTunnelReflected(AF_INET6, AF_INET, true, false); |
| } |
| |
| @Test |
| public void testTunnelV6InV6() throws Exception { |
| checkTunnelOutput(AF_INET6, AF_INET6, false, false); |
| checkTunnelInput(AF_INET6, AF_INET6, false, false); |
| } |
| |
| @Test |
| public void testTunnelV6InV6Reflected() throws Exception { |
| checkTunnelReflected(AF_INET6, AF_INET6, false, false); |
| } |
| } |