Merge "Add UDP encap socket test to CTS tests"
This commit is contained in:
@@ -22,13 +22,15 @@ import android.net.IpSecAlgorithm;
|
|||||||
import android.net.IpSecManager;
|
import android.net.IpSecManager;
|
||||||
import android.net.IpSecTransform;
|
import android.net.IpSecTransform;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.system.ErrnoException;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
import android.system.OsConstants;
|
import android.system.OsConstants;
|
||||||
import android.test.AndroidTestCase;
|
import android.test.AndroidTestCase;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.net.DatagramSocket;
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@@ -40,34 +42,36 @@ public class IpSecManagerTest extends AndroidTestCase {
|
|||||||
|
|
||||||
private ConnectivityManager mCM;
|
private ConnectivityManager mCM;
|
||||||
|
|
||||||
private static final InetAddress GOOGLE_DNS_4;
|
private static InetAddress IpAddress(String addrString) {
|
||||||
private static final InetAddress GOOGLE_DNS_6;
|
|
||||||
|
|
||||||
static {
|
|
||||||
try {
|
try {
|
||||||
// Google Public DNS Addresses;
|
return InetAddress.getByName(addrString);
|
||||||
GOOGLE_DNS_4 = InetAddress.getByName("8.8.8.8");
|
|
||||||
GOOGLE_DNS_6 = InetAddress.getByName("2001:4860:4860::8888");
|
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
throw new RuntimeException("Could not resolve DNS Addresses", e);
|
throw new IllegalArgumentException("Invalid IP address: " + e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final InetAddress GOOGLE_DNS_4 = IpAddress("8.8.8.8");
|
||||||
|
private static final InetAddress GOOGLE_DNS_6 = IpAddress("2001:4860:4860::8888");
|
||||||
|
private static final InetAddress LOOPBACK_4 = IpAddress("127.0.0.1");
|
||||||
|
|
||||||
private static final InetAddress[] GOOGLE_DNS_LIST =
|
private static final InetAddress[] GOOGLE_DNS_LIST =
|
||||||
new InetAddress[] {GOOGLE_DNS_4, GOOGLE_DNS_6};
|
new InetAddress[] {GOOGLE_DNS_4, GOOGLE_DNS_6};
|
||||||
|
|
||||||
private static final int DROID_SPI = 0xD1201D;
|
private static final int DROID_SPI = 0xD1201D;
|
||||||
|
private static final int MAX_PORT_BIND_ATTEMPTS = 10;
|
||||||
|
|
||||||
private static final byte[] CRYPT_KEY =
|
private static final byte[] CRYPT_KEY = {
|
||||||
new byte[] {
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
|
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||||
0x0E, 0x0F
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||||
};
|
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
|
||||||
private static final byte[] AUTH_KEY =
|
};
|
||||||
new byte[] {
|
private static final byte[] AUTH_KEY = {
|
||||||
0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x7F
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
|
||||||
};
|
0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F
|
||||||
|
};
|
||||||
|
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
super.setUp();
|
super.setUp();
|
||||||
@@ -119,7 +123,7 @@ public class IpSecManagerTest extends AndroidTestCase {
|
|||||||
* send data (expect exception)
|
* send data (expect exception)
|
||||||
*/
|
*/
|
||||||
public void testCreateTransform() throws Exception {
|
public void testCreateTransform() throws Exception {
|
||||||
InetAddress local = InetAddress.getLoopbackAddress();
|
InetAddress local = LOOPBACK_4;
|
||||||
IpSecManager.SecurityParameterIndex outSpi =
|
IpSecManager.SecurityParameterIndex outSpi =
|
||||||
mISM.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, local);
|
mISM.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, local);
|
||||||
|
|
||||||
@@ -168,4 +172,231 @@ public class IpSecManagerTest extends AndroidTestCase {
|
|||||||
Os.close(udpSocket);
|
Os.close(udpSocket);
|
||||||
transform.close();
|
transform.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testOpenUdpEncapSocketSpecificPort() throws Exception {
|
||||||
|
IpSecManager.UdpEncapsulationSocket encapSocket = null;
|
||||||
|
int port = -1;
|
||||||
|
for (int i = 0; i < MAX_PORT_BIND_ATTEMPTS; i++) {
|
||||||
|
try {
|
||||||
|
port = findUnusedPort();
|
||||||
|
encapSocket = mISM.openUdpEncapsulationSocket(port);
|
||||||
|
break;
|
||||||
|
} catch (ErrnoException e) {
|
||||||
|
if (e.errno == OsConstants.EADDRINUSE) {
|
||||||
|
// Someone claimed the port since we called findUnusedPort.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
if (encapSocket != null) {
|
||||||
|
encapSocket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encapSocket == null) {
|
||||||
|
fail("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue("Returned invalid port", encapSocket.getPort() == port);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testOpenUdpEncapSocketRandomPort() throws Exception {
|
||||||
|
try (IpSecManager.UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
|
||||||
|
assertTrue("Returned invalid port", encapSocket.getPort() != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testUdpEncapsulation() throws Exception {
|
||||||
|
InetAddress local = LOOPBACK_4;
|
||||||
|
|
||||||
|
// 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 outSpi =
|
||||||
|
mISM.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, local);
|
||||||
|
IpSecManager.SecurityParameterIndex inSpi =
|
||||||
|
mISM.reserveSecurityParameterIndex(
|
||||||
|
IpSecTransform.DIRECTION_IN, local, outSpi.getSpi());
|
||||||
|
IpSecTransform transform =
|
||||||
|
buildIpSecTransform(mContext, inSpi, outSpi, encapSocket, local)) {
|
||||||
|
|
||||||
|
// Create user socket, apply transform to it
|
||||||
|
FileDescriptor udpSocket = null;
|
||||||
|
try {
|
||||||
|
udpSocket = getTestV4UdpSocket(local);
|
||||||
|
int port = getPort(udpSocket);
|
||||||
|
|
||||||
|
mISM.applyTransportModeTransform(udpSocket, 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.getSocket(),
|
||||||
|
data,
|
||||||
|
0,
|
||||||
|
data.length,
|
||||||
|
0,
|
||||||
|
local,
|
||||||
|
encapSocket.getPort());
|
||||||
|
in = new byte[data.length];
|
||||||
|
Os.read(encapSocket.getSocket(), in, 0, in.length);
|
||||||
|
assertTrue(
|
||||||
|
"Encap socket was unable to send/receive IKE data",
|
||||||
|
Arrays.equals(data, in));
|
||||||
|
|
||||||
|
mISM.removeTransportModeTransform(udpSocket, transform);
|
||||||
|
} finally {
|
||||||
|
if (udpSocket != null) {
|
||||||
|
Os.close(udpSocket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testIke() throws Exception {
|
||||||
|
InetAddress local = LOOPBACK_4;
|
||||||
|
|
||||||
|
// 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 outSpi =
|
||||||
|
mISM.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, local);
|
||||||
|
IpSecManager.SecurityParameterIndex inSpi =
|
||||||
|
mISM.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_IN, local);
|
||||||
|
IpSecTransform transform =
|
||||||
|
buildIpSecTransform(mContext, inSpi, outSpi, encapSocket, local)) {
|
||||||
|
|
||||||
|
// Create user socket, apply transform to it
|
||||||
|
FileDescriptor sock = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
sock = getTestV4UdpSocket(local);
|
||||||
|
int port = getPort(sock);
|
||||||
|
|
||||||
|
mISM.applyTransportModeTransform(sock, 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, local, 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.getSocket(),
|
||||||
|
data,
|
||||||
|
0,
|
||||||
|
data.length,
|
||||||
|
0,
|
||||||
|
local,
|
||||||
|
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.getSocket(), in, 0, in.length);
|
||||||
|
assertTrue(
|
||||||
|
"Encap socket received UDP-encap-ESP data despite invalid SPIs",
|
||||||
|
Arrays.equals(header, in));
|
||||||
|
|
||||||
|
mISM.removeTransportModeTransform(sock, transform);
|
||||||
|
} finally {
|
||||||
|
if (sock != null) {
|
||||||
|
Os.close(sock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This function finds an available port */
|
||||||
|
private static int findUnusedPort() throws Exception {
|
||||||
|
// Get an available port.
|
||||||
|
ServerSocket s = new ServerSocket(0);
|
||||||
|
int port = s.getLocalPort();
|
||||||
|
s.close();
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IpSecTransform buildIpSecTransform(
|
||||||
|
Context mContext,
|
||||||
|
IpSecManager.SecurityParameterIndex inSpi,
|
||||||
|
IpSecManager.SecurityParameterIndex outSpi,
|
||||||
|
IpSecManager.UdpEncapsulationSocket encapSocket,
|
||||||
|
InetAddress remoteAddr)
|
||||||
|
throws Exception {
|
||||||
|
return new IpSecTransform.Builder(mContext)
|
||||||
|
.setSpi(IpSecTransform.DIRECTION_IN, inSpi)
|
||||||
|
.setSpi(IpSecTransform.DIRECTION_OUT, outSpi)
|
||||||
|
.setEncryption(
|
||||||
|
IpSecTransform.DIRECTION_IN,
|
||||||
|
new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
|
||||||
|
.setEncryption(
|
||||||
|
IpSecTransform.DIRECTION_OUT,
|
||||||
|
new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
|
||||||
|
.setAuthentication(
|
||||||
|
IpSecTransform.DIRECTION_IN,
|
||||||
|
new IpSecAlgorithm(
|
||||||
|
IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4))
|
||||||
|
.setAuthentication(
|
||||||
|
IpSecTransform.DIRECTION_OUT,
|
||||||
|
new IpSecAlgorithm(
|
||||||
|
IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4))
|
||||||
|
.setIpv4Encapsulation(encapSocket, encapSocket.getPort())
|
||||||
|
.buildTransportModeTransform(remoteAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getPort(FileDescriptor sock) throws Exception {
|
||||||
|
return ((InetSocketAddress) Os.getsockname(sock)).getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FileDescriptor getTestV4UdpSocket(InetAddress v4Addr) throws Exception {
|
||||||
|
FileDescriptor sock =
|
||||||
|
Os.socket(OsConstants.AF_INET, OsConstants.SOCK_DGRAM, OsConstants.IPPROTO_UDP);
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_PORT_BIND_ATTEMPTS; i++) {
|
||||||
|
try {
|
||||||
|
int port = findUnusedPort();
|
||||||
|
Os.bind(sock, v4Addr, port);
|
||||||
|
break;
|
||||||
|
} catch (ErrnoException e) {
|
||||||
|
// Someone claimed the port since we called findUnusedPort.
|
||||||
|
if (e.errno == OsConstants.EADDRINUSE) {
|
||||||
|
if (i == MAX_PORT_BIND_ATTEMPTS - 1) {
|
||||||
|
|
||||||
|
fail("Failed " + MAX_PORT_BIND_ATTEMPTS + " attempts to bind to a port");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw e.rethrowAsIOException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user