Merge "Prevent CTS from hanging if no UDP packet was received" am: a48cb94938

am: 2a1b3d9320

Change-Id: Ia7bf725b3f30f3f288062ae170e8e234c2bc1c60
This commit is contained in:
Benedict Wong
2018-03-30 19:06:11 +00:00
committed by android-build-merger
2 changed files with 80 additions and 152 deletions

View File

@@ -25,6 +25,7 @@ import android.net.IpSecTransform;
import android.system.Os;
import android.system.OsConstants;
import android.test.AndroidTestCase;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -38,6 +39,7 @@ import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
public class IpSecBaseTest extends AndroidTestCase {
@@ -51,6 +53,7 @@ public class IpSecBaseTest extends AndroidTestCase {
protected static final byte[] TEST_DATA = "Best test data ever!".getBytes();
protected static final int DATA_BUFFER_LEN = 4096;
protected static final int SOCK_TIMEOUT = 500;
private static final byte[] KEY_DATA = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
@@ -124,9 +127,27 @@ public class IpSecBaseTest extends AndroidTestCase {
@Override
public byte[] receive() throws Exception {
byte[] in = new byte[DATA_BUFFER_LEN];
int bytesRead = Os.read(mFd, in, 0, DATA_BUFFER_LEN);
AtomicInteger bytesRead = new AtomicInteger(-1);
return Arrays.copyOfRange(in, 0, bytesRead);
Thread readSockThread = new Thread(() -> {
long startTime = System.currentTimeMillis();
while (bytesRead.get() < 0 && System.currentTimeMillis() < startTime + SOCK_TIMEOUT) {
try {
bytesRead.set(Os.recvfrom(mFd, in, 0, DATA_BUFFER_LEN, 0, null));
} catch (Exception e) {
Log.e(TAG, "Error encountered reading from socket", e);
}
}
});
readSockThread.start();
readSockThread.join(SOCK_TIMEOUT);
if (bytesRead.get() < 0) {
throw new IOException("No data received from socket");
}
return Arrays.copyOfRange(in, 0, bytesRead.get());
}
@Override
@@ -174,7 +195,7 @@ public class IpSecBaseTest extends AndroidTestCase {
public JavaUdpSocket(InetAddress localAddr) {
try {
mSocket = new DatagramSocket(0, localAddr);
mSocket.setSoTimeout(500);
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.
@@ -227,7 +248,7 @@ public class IpSecBaseTest extends AndroidTestCase {
public JavaTcpSocket(Socket socket) {
mSocket = socket;
try {
mSocket.setSoTimeout(500);
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.
@@ -279,7 +300,7 @@ public class IpSecBaseTest extends AndroidTestCase {
}
}
private static void applyTransformBidirectionally(
protected static void applyTransformBidirectionally(
IpSecManager ism, IpSecTransform transform, GenericSocket socket) throws Exception {
for (int direction : DIRECTIONS) {
socket.applyTransportModeTransform(ism, direction, transform);

View File

@@ -576,34 +576,68 @@ public class IpSecManagerTest extends IpSecBaseTest {
}
}
public void testIkeOverUdpEncapSocket() throws Exception {
// IPv6 not supported for UDP-encap-ESP
InetAddress local = InetAddress.getByName(IPV4_LOOPBACK);
private void checkIkePacket(
NativeUdpSocket wrappedEncapSocket, InetAddress localAddr) throws Exception {
StatsChecker.initStatsChecker();
try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
int localPort = getPort(encapSocket.getFileDescriptor());
try (NativeUdpSocket remoteSocket = new NativeUdpSocket(getBoundUdpSocket(localAddr))) {
// Append ESP header - 4 bytes of SPI, 4 bytes of seq number
// Append IKE/ESP header - 4 bytes of SPI, 4 bytes of seq number, all zeroed out
// If the first four bytes are zero, assume non-ESP (IKE traffic)
byte[] dataWithEspHeader = new byte[TEST_DATA.length + 8];
System.arraycopy(TEST_DATA, 0, dataWithEspHeader, 8, TEST_DATA.length);
byte[] in = new byte[dataWithEspHeader.length];
Os.sendto(
encapSocket.getFileDescriptor(),
dataWithEspHeader,
0,
dataWithEspHeader.length,
0,
local,
localPort);
Os.read(encapSocket.getFileDescriptor(), in, 0, in.length);
// Send the IKE packet from remoteSocket to wrappedEncapSocket. Since IKE packets
// are multiplexed over the socket, we expect them to appear on the encap socket
// (as opposed to being decrypted and received on the non-encap socket)
remoteSocket.sendTo(dataWithEspHeader, localAddr, wrappedEncapSocket.getPort());
byte[] in = wrappedEncapSocket.receive();
assertArrayEquals("Encapsulated data did not match.", dataWithEspHeader, in);
int ipHdrLen = local instanceof Inet6Address ? IP6_HDRLEN : IP4_HDRLEN;
int expectedPacketSize = dataWithEspHeader.length + UDP_HDRLEN + ipHdrLen;
StatsChecker.assertUidStatsDelta(expectedPacketSize, 1, expectedPacketSize, 1);
StatsChecker.assertIfaceStatsDelta(expectedPacketSize, 1, expectedPacketSize, 1);
// Also test that the IKE socket can send data out.
wrappedEncapSocket.sendTo(dataWithEspHeader, localAddr, remoteSocket.getPort());
in = remoteSocket.receive();
assertArrayEquals("Encapsulated data did not match.", dataWithEspHeader, in);
// Calculate expected packet sizes. Always use IPv4 header, since our kernels only
// guarantee support of UDP encap on IPv4.
int expectedNumPkts = 2;
int expectedPacketSize =
expectedNumPkts * (dataWithEspHeader.length + UDP_HDRLEN + IP4_HDRLEN);
StatsChecker.waitForNumPackets(expectedNumPkts);
StatsChecker.assertUidStatsDelta(
expectedPacketSize, expectedNumPkts, expectedPacketSize, expectedNumPkts);
StatsChecker.assertIfaceStatsDelta(
expectedPacketSize, expectedNumPkts, expectedPacketSize, expectedNumPkts);
}
}
public void testIkeOverUdpEncapSocket() throws Exception {
// IPv6 not supported for UDP-encap-ESP
InetAddress local = InetAddress.getByName(IPV4_LOOPBACK);
try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
NativeUdpSocket wrappedEncapSocket =
new NativeUdpSocket(encapSocket.getFileDescriptor());
checkIkePacket(wrappedEncapSocket, local);
// Now try with a transform applied to a socket using this Encap socket
IpSecAlgorithm crypt = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
IpSecAlgorithm auth = new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_MD5, getKey(128), 96);
try (IpSecManager.SecurityParameterIndex spi =
mISM.allocateSecurityParameterIndex(local);
IpSecTransform transform =
new IpSecTransform.Builder(mContext)
.setEncryption(crypt)
.setAuthentication(auth)
.setIpv4Encapsulation(encapSocket, encapSocket.getPort())
.buildTransportModeTransform(local, spi);
JavaUdpSocket localSocket = new JavaUdpSocket(local)) {
applyTransformBidirectionally(mISM, transform, localSocket);
checkIkePacket(wrappedEncapSocket, local);
}
}
}
@@ -1096,131 +1130,4 @@ public class IpSecManagerTest extends IpSecBaseTest {
assertTrue("Returned invalid port", encapSocket.getPort() != 0);
}
}
public void testUdpEncapsulation() throws Exception {
InetAddress local = InetAddress.getByName(IPV4_LOOPBACK);
// TODO: Refactor to make this more representative of a normal application use case. (use
// separate sockets for inbound and outbound)
// Create SPIs, UDP encap socket
try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket();
IpSecManager.SecurityParameterIndex spi =
mISM.allocateSecurityParameterIndex(local);
IpSecTransform transform =
buildIpSecTransform(mContext, spi, encapSocket, local)) {
// Create user socket, apply transform to it
FileDescriptor udpSocket = null;
try {
udpSocket = getBoundUdpSocket(local);
int port = getPort(udpSocket);
mISM.applyTransportModeTransform(
udpSocket, IpSecManager.DIRECTION_IN, transform);
mISM.applyTransportModeTransform(
udpSocket, IpSecManager.DIRECTION_OUT, transform);
// Send an ESP packet from this socket to itself. Since the inbound and
// outbound transforms match, we should receive the data we sent.
byte[] data = new String("IPSec UDP-encap-ESP test data").getBytes("UTF-8");
Os.sendto(udpSocket, data, 0, data.length, 0, local, port);
byte[] in = new byte[data.length];
Os.read(udpSocket, in, 0, in.length);
assertTrue("Encapsulated data did not match.", Arrays.equals(data, in));
// Send an IKE packet from this socket to itself. IKE packets (SPI of 0)
// are not transformed in any way, and should be sent in the clear
// We expect this to work too (no inbound transforms)
final byte[] header = new byte[] {0, 0, 0, 0};
final String message = "Sample IKE Packet";
data = (new String(header) + message).getBytes("UTF-8");
Os.sendto(
encapSocket.getFileDescriptor(),
data,
0,
data.length,
0,
local,
encapSocket.getPort());
in = new byte[data.length];
Os.read(encapSocket.getFileDescriptor(), in, 0, in.length);
assertTrue(
"Encap socket was unable to send/receive IKE data",
Arrays.equals(data, in));
mISM.removeTransportModeTransforms(udpSocket);
} finally {
if (udpSocket != null) {
Os.close(udpSocket);
}
}
}
}
public void testIke() throws Exception {
InetAddress localAddr = InetAddress.getByName(IPV4_LOOPBACK);
// TODO: Refactor to make this more representative of a normal application use case. (use
// separate sockets for inbound and outbound)
try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket();
IpSecManager.SecurityParameterIndex spi =
mISM.allocateSecurityParameterIndex(localAddr);
IpSecTransform transform =
buildIpSecTransform(mContext, spi, encapSocket, localAddr)) {
// Create user socket, apply transform to it
FileDescriptor sock = null;
try {
sock = getBoundUdpSocket(localAddr);
int port = getPort(sock);
mISM.applyTransportModeTransform(sock, IpSecManager.DIRECTION_IN, transform);
mISM.applyTransportModeTransform(sock, IpSecManager.DIRECTION_OUT, transform);
// TODO: Find a way to set a timeout on the socket, and assert the ESP packet
// doesn't make it through. Setting sockopts currently throws EPERM (possibly
// because it is owned by a different UID).
// Send ESP packet from our socket to the encap socket. The SPIs do not
// match, and we should expect this packet to be dropped.
byte[] header = new byte[] {1, 1, 1, 1};
String message = "Sample ESP Packet";
byte[] data = (new String(header) + message).getBytes("UTF-8");
Os.sendto(sock, data, 0, data.length, 0, localAddr, encapSocket.getPort());
// Send IKE packet from the encap socket to itself. Since IKE is not
// transformed in any way, this should succeed.
header = new byte[] {0, 0, 0, 0};
message = "Sample IKE Packet";
data = (new String(header) + message).getBytes("UTF-8");
Os.sendto(
encapSocket.getFileDescriptor(),
data,
0,
data.length,
0,
localAddr,
encapSocket.getPort());
// ESP data should be dropped, due to different input SPI (as opposed to being
// readable from the encapSocket)
// Thus, only IKE data should be received from the socket.
// If the first four bytes are zero, assume non-ESP (IKE) traffic.
// Expect an nulled out SPI just as we sent out, without being modified.
byte[] in = new byte[4];
in[0] = 1; // Make sure the array has to be overwritten to pass
Os.read(encapSocket.getFileDescriptor(), in, 0, in.length);
assertTrue(
"Encap socket received UDP-encap-ESP data despite invalid SPIs",
Arrays.equals(header, in));
mISM.removeTransportModeTransforms(sock);
} finally {
if (sock != null) {
Os.close(sock);
}
}
}
}
}