Add IPsec Tunnel mode data tests

This change adds single-direction tests for the IPsec Tunnel Mode API.
In the outbound direction, TUNs are used to capture outgoing packets,
and values are inspected. In the inbound direction, packets are built
manually, using the PacketUtils framework. Additional testing for
end-to-end integration tests will follow in aosp/941021 using packet
reflection via the TUN.

Bug: 72950854
Test: This; passing
Change-Id: Ic4181fc857fa880db5553314efa914f870dbe87c
This commit is contained in:
Benedict Wong
2019-04-08 10:15:26 -07:00
parent 75fb3d002e
commit 64e64ff454
3 changed files with 661 additions and 83 deletions

View File

@@ -28,11 +28,12 @@ import android.system.OsConstants;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import android.util.Log; import android.util.Log;
import androidx.test.InstrumentationRegistry;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.Inet6Address; import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@@ -72,8 +73,14 @@ public class IpSecBaseTest extends AndroidTestCase {
protected void setUp() throws Exception { protected void setUp() throws Exception {
super.setUp(); super.setUp();
mISM = (IpSecManager) getContext().getSystemService(Context.IPSEC_SERVICE); mISM =
mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE); (IpSecManager)
InstrumentationRegistry.getContext()
.getSystemService(Context.IPSEC_SERVICE);
mCM =
(ConnectivityManager)
InstrumentationRegistry.getContext()
.getSystemService(Context.CONNECTIVITY_SERVICE);
} }
protected static byte[] getKey(int bitLength) { protected static byte[] getKey(int bitLength) {
@@ -195,6 +202,17 @@ public class IpSecBaseTest extends AndroidTestCase {
public static class JavaUdpSocket implements GenericUdpSocket { public static class JavaUdpSocket implements GenericUdpSocket {
public final DatagramSocket mSocket; 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) { public JavaUdpSocket(InetAddress localAddr) {
try { try {
mSocket = new DatagramSocket(0, localAddr); mSocket = new DatagramSocket(0, localAddr);
@@ -425,26 +443,25 @@ public class IpSecBaseTest extends AndroidTestCase {
} }
protected static IpSecTransform buildIpSecTransform( protected static IpSecTransform buildIpSecTransform(
Context mContext, Context context,
IpSecManager.SecurityParameterIndex spi, IpSecManager.SecurityParameterIndex spi,
IpSecManager.UdpEncapsulationSocket encapSocket, IpSecManager.UdpEncapsulationSocket encapSocket,
InetAddress remoteAddr) InetAddress remoteAddr)
throws Exception { throws Exception {
String localAddr = (remoteAddr instanceof Inet4Address) ? IPV4_LOOPBACK : IPV6_LOOPBACK;
IpSecTransform.Builder builder = IpSecTransform.Builder builder =
new IpSecTransform.Builder(mContext) new IpSecTransform.Builder(context)
.setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY)) .setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
.setAuthentication( .setAuthentication(
new IpSecAlgorithm( new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA256, IpSecAlgorithm.AUTH_HMAC_SHA256,
AUTH_KEY, AUTH_KEY,
AUTH_KEY.length * 4)); AUTH_KEY.length * 4));
if (encapSocket != null) { if (encapSocket != null) {
builder.setIpv4Encapsulation(encapSocket, encapSocket.getPort()); builder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
} }
return builder.buildTransportModeTransform(InetAddress.getByName(localAddr), spi); return builder.buildTransportModeTransform(remoteAddr, spi);
} }
private IpSecTransform buildDefaultTransform(InetAddress localAddr) throws Exception { private IpSecTransform buildDefaultTransform(InetAddress localAddr) throws Exception {

View File

@@ -16,174 +16,728 @@
package android.net.cts; 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.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.IpSecAlgorithm; import android.net.IpSecAlgorithm;
import android.net.IpSecManager; import android.net.IpSecManager;
import android.net.IpSecTransform; import android.net.IpSecTransform;
import android.net.LinkAddress;
import android.net.Network; 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 com.android.compatibility.common.util.SystemUtil;
import java.net.Inet4Address;
import java.net.Inet6Address; import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InterfaceAddress; import java.net.InterfaceAddress;
import java.net.NetworkInterface; 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 { public class IpSecManagerTunnelTest extends IpSecBaseTest {
private static final String TAG = IpSecManagerTunnelTest.class.getSimpleName(); 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 static final InetAddress LOCAL_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.1");
private Network mIpSecNetwork; 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(); 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() { @AfterClass
setAppop(false); 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() { private static boolean hasTunnelsFeature() {
return getContext() return sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS);
.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS);
} }
private void setAppop(boolean allow) { private static void setAppop(int appop, boolean allow) {
// Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted by the String opName = AppOpsManager.opToName(appop);
// telephony framework, and the only permission that is sufficient is NETWORK_STACK. So we for (String pkg : new String[] {"com.android.shell", sContext.getPackageName()}) {
// shell out the appop manager, to give us the right appop permissions. String cmd =
String cmd = String.format(
"appops set " "appops set %s %s %s",
+ mContext.getPackageName() pkg, // Package name
+ " MANAGE_IPSEC_TUNNELS " opName, // Appop
+ (allow ? "allow" : "deny"); (allow ? "allow" : "deny")); // Action
SystemUtil.runShellCommand(cmd); 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; if (!hasTunnelsFeature()) return;
// Ensure we don't have the appop. Permission is not requested in the Manifest // 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 // Security exceptions are thrown regardless of IPv4/IPv6. Just test one
try { 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"); fail("Did not throw SecurityException for Tunnel creation without appop");
} catch (SecurityException expected) { } catch (SecurityException expected) {
} }
} }
public void testSecurityExceptionsBuildTunnelTransform() throws Exception { @Test
public void testSecurityExceptionBuildTunnelTransformWithoutAppop() throws Exception {
if (!hasTunnelsFeature()) return; if (!hasTunnelsFeature()) return;
// Ensure we don't have the appop. Permission is not requested in the Manifest // 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 // Security exceptions are thrown regardless of IPv4/IPv6. Just test one
try (IpSecManager.SecurityParameterIndex spi = try (IpSecManager.SecurityParameterIndex spi =
mISM.allocateSecurityParameterIndex(OUTER_ADDR4); mISM.allocateSecurityParameterIndex(LOCAL_INNER_4);
IpSecTransform transform = IpSecTransform transform =
new IpSecTransform.Builder(mContext) new IpSecTransform.Builder(sContext)
.buildTunnelModeTransform(OUTER_ADDR4, spi)) { .buildTunnelModeTransform(REMOTE_INNER_4, spi)) {
fail("Did not throw SecurityException for Transform creation without appop"); fail("Did not throw SecurityException for Transform creation without appop");
} catch (SecurityException expected) { } 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<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 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 { throws Exception {
if (!hasTunnelsFeature()) return; if (!hasTunnelsFeature()) return;
setAppop(true); InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6;
int innerPrefixLen = inner instanceof Inet6Address ? IP6_PREFIX_LEN : IP4_PREFIX_LEN; 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 = IpSecManager.IpSecTunnelInterface tunnelIntf =
mISM.createIpSecTunnelInterface(outer, outer, mCM.getActiveNetwork()); mISM.createIpSecTunnelInterface(localOuter, remoteOuter, sTunNetwork)) {
IpSecManager.UdpEncapsulationSocket encapSocket = // Build the test network
mISM.openUdpEncapsulationSocket()) { 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<InterfaceAddress> 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( transformBuilder.setEncryption(
new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY)); new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY));
transformBuilder.setAuthentication( transformBuilder.setAuthentication(
new IpSecAlgorithm( new IpSecAlgorithm(
IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4)); IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4));
if (useEncap) { if (encapSocket != null) {
transformBuilder.setIpv4Encapsulation(encapSocket, encapSocket.getPort()); transformBuilder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
} }
// Check transform application // Apply transform and check that traffic is properly encrypted
try (IpSecTransform transform = transformBuilder.buildTunnelModeTransform(outer, spi)) { try (IpSecTransform inTransform =
mISM.applyTunnelModeTransform(tunnelIntf, IpSecManager.DIRECTION_IN, transform); transformBuilder.buildTunnelModeTransform(remoteOuter, inSpi);
mISM.applyTunnelModeTransform(tunnelIntf, IpSecManager.DIRECTION_OUT, transform); 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 // Teardown the test network
NetworkInterface netIntf = NetworkInterface.getByName(tunnelIntf.getInterfaceName()); sTNM.teardownTestNetwork(testNetwork);
assertNotNull(netIntf);
// Add addresses and check
tunnelIntf.addAddress(inner, innerPrefixLen);
for (InterfaceAddress intfAddr : netIntf.getInterfaceAddresses()) {
assertEquals(intfAddr.getAddress(), inner);
assertEquals(intfAddr.getNetworkPrefixLength(), innerPrefixLen);
}
// Remove addresses and check // Remove addresses and check
tunnelIntf.removeAddress(inner, innerPrefixLen); tunnelIntf.removeAddress(localInner, innerPrefixLen);
netIntf = NetworkInterface.getByName(tunnelIntf.getInterfaceName());
assertTrue(netIntf.getInterfaceAddresses().isEmpty()); assertTrue(netIntf.getInterfaceAddresses().isEmpty());
// Check interface was cleaned up // Check interface was cleaned up
tunnelIntf.close(); tunnelIntf.close();
netIntf = NetworkInterface.getByName(tunnelIntf.getInterfaceName()); netIntf = NetworkInterface.getByName(tunnelIntf.getInterfaceName());
assertNull(netIntf); assertNull(netIntf);
} finally {
if (testNetworkCb != null) {
sCM.unregisterNetworkCallback(testNetworkCb);
}
} }
} }
/* private static void receiveAndValidatePacket(JavaUdpSocket socket) throws Exception {
* Create, add and remove addresses, then teardown tunnel 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 { 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 { 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 { 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 { 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 { 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 { 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);
} }
} }

View File

@@ -247,4 +247,11 @@ public class TunUtils {
out.write(pkt); out.write(pkt);
out.flush(); out.flush();
} }
/** Resets the intercepted packets. */
public void reset() throws IOException {
synchronized (mPackets) {
mPackets.clear();
}
}
} }