blob: 5f15f5544f855e0c3a565abceea7241046e07cfe [file] [log] [blame]
/*
* Copyright (C) 2019 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 com.android.ike.ikev2;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.net.IpSecManager;
import android.net.IpSecManager.UdpEncapsulationSocket;
import android.os.HandlerThread;
import android.os.Looper;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
import android.util.LongSparseArray;
import androidx.test.InstrumentationRegistry;
import com.android.ike.ikev2.IkeSocket.PacketReceiver;
import com.android.ike.ikev2.message.IkeHeader;
import com.android.ike.ikev2.message.TestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import java.io.FileDescriptor;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public final class IkeSocketTest {
private static final int REMOTE_RECV_BUFF_SIZE = 2048;
private static final int TIMEOUT = 1000;
private static final String NON_ESP_MARKER_HEX_STRING = "00000000";
private static final String IKE_REQ_MESSAGE_HEX_STRING =
"5f54bf6d8b48e6e100000000000000002120220800000000"
+ "00000150220000300000002c010100040300000c0100000c"
+ "800e00800300000803000002030000080400000200000008"
+ "020000022800008800020000b4a2faf4bb54878ae21d6385"
+ "12ece55d9236fc5046ab6cef82220f421f3ce6361faf3656"
+ "4ecb6d28798a94aad7b2b4b603ddeaaa5630adb9ece8ac37"
+ "534036040610ebdd92f46bef84f0be7db860351843858f8a"
+ "cf87056e272377f70c9f2d81e29c7b0ce4f291a3a72476bb"
+ "0b278fd4b7b0a4c26bbeb08214c707137607958729000024"
+ "c39b7f368f4681b89fa9b7be6465abd7c5f68b6ed5d3b4c7"
+ "2cb4240eb5c464122900001c00004004e54f73b7d83f6beb"
+ "881eab2051d8663f421d10b02b00001c00004005d915368c"
+ "a036004cb578ae3e3fb268509aeab1900000002069936922"
+ "8741c6d4ca094c93e242c9de19e7b7c60000000500000500";
private static final String LOCAL_SPI = "0000000000000000";
private static final String REMOTE_SPI = "5f54bf6d8b48e6e1";
private static final String DATA_ONE = "one 1";
private static final String DATA_TWO = "two 2";
private static final String IPV4_LOOPBACK = "127.0.0.1";
private byte[] mDataOne;
private byte[] mDataTwo;
private long mLocalSpi;
private long mRemoteSpi;
private LongSparseArray mSpiToIkeStateMachineMap;
private PacketReceiver mPacketReceiver;
private UdpEncapsulationSocket mClientUdpEncapSocket;
private InetAddress mLocalAddress;
private FileDescriptor mDummyRemoteServerFd;
private IkeSessionStateMachine mMockIkeSessionStateMachine;
@Before
public void setUp() throws Exception {
Context context = InstrumentationRegistry.getContext();
IpSecManager ipSecManager = (IpSecManager) context.getSystemService(Context.IPSEC_SERVICE);
mClientUdpEncapSocket = ipSecManager.openUdpEncapsulationSocket();
mLocalAddress = InetAddress.getByName(IPV4_LOOPBACK);
mDummyRemoteServerFd = getBoundUdpSocket(mLocalAddress);
mDataOne = DATA_ONE.getBytes("UTF-8");
mDataTwo = DATA_TWO.getBytes("UTF-8");
ByteBuffer localSpiBuffer = ByteBuffer.wrap(TestUtils.hexStringToByteArray(LOCAL_SPI));
mLocalSpi = localSpiBuffer.getLong();
ByteBuffer remoteSpiBuffer = ByteBuffer.wrap(TestUtils.hexStringToByteArray(REMOTE_SPI));
mRemoteSpi = remoteSpiBuffer.getLong();
mMockIkeSessionStateMachine = mock(IkeSessionStateMachine.class);
mSpiToIkeStateMachineMap = new LongSparseArray<IkeSessionStateMachine>();
mSpiToIkeStateMachineMap.put(mLocalSpi, mMockIkeSessionStateMachine);
mPacketReceiver = new IkeSocket.PacketReceiver();
}
@After
public void tearDown() throws Exception {
mClientUdpEncapSocket.close();
IkeSocket.setPacketReceiver(mPacketReceiver);
Os.close(mDummyRemoteServerFd);
}
private static FileDescriptor getBoundUdpSocket(InetAddress address) throws Exception {
FileDescriptor sock =
Os.socket(OsConstants.AF_INET, OsConstants.SOCK_DGRAM, OsConstants.IPPROTO_UDP);
Os.bind(sock, address, IkeSocket.IKE_SERVER_PORT);
return sock;
}
@Test
public void testGetAndCloseIkeSocket() throws Exception {
if (Looper.myLooper() == null) Looper.myLooper().prepare();
IkeSocket ikeSocketOne = IkeSocket.getIkeSocket(mClientUdpEncapSocket);
assertEquals(1, ikeSocketOne.mRefCount);
IkeSocket ikeSocketTwo = IkeSocket.getIkeSocket(mClientUdpEncapSocket);
assertEquals(ikeSocketOne, ikeSocketTwo);
assertEquals(2, ikeSocketTwo.mRefCount);
ikeSocketOne.releaseReference();
assertEquals(1, ikeSocketOne.mRefCount);
ikeSocketTwo.releaseReference();
assertEquals(0, ikeSocketTwo.mRefCount);
}
@Test
public void testSendIkePacket() throws Exception {
if (Looper.myLooper() == null) Looper.myLooper().prepare();
// Send IKE packet
IkeSocket ikeSocket = IkeSocket.getIkeSocket(mClientUdpEncapSocket);
ikeSocket.sendIkePacket(mDataOne, mLocalAddress);
byte[] receivedData = receive(mDummyRemoteServerFd);
// Verify received data
ByteBuffer expectedBuffer =
ByteBuffer.allocate(IkeSocket.NON_ESP_MARKER_LEN + mDataOne.length);
expectedBuffer.put(IkeSocket.NON_ESP_MARKER).put(mDataOne);
assertArrayEquals(expectedBuffer.array(), receivedData);
ikeSocket.releaseReference();
}
@Test
public void testReceiveIkePacket() throws Exception {
// Create working thread.
HandlerThread mIkeThread = new HandlerThread("IkeSocketTest");
mIkeThread.start();
// Create IkeSocket on working thread.
IkeSocketReceiver socketReceiver = new IkeSocketReceiver();
TestCountDownLatch createLatch = new TestCountDownLatch();
mIkeThread
.getThreadHandler()
.post(
() -> {
try {
socketReceiver.setIkeSocket(
IkeSocket.getIkeSocket(mClientUdpEncapSocket));
createLatch.countDown();
Log.d("IkeSocketTest", "IkeSocket created.");
} catch (ErrnoException e) {
Log.e("IkeSocketTest", "error encountered creating IkeSocket ", e);
}
});
createLatch.await();
IkeSocket ikeSocket = socketReceiver.getIkeSocket();
assertNotNull(ikeSocket);
// Configure IkeSocket
TestCountDownLatch receiveLatch = new TestCountDownLatch();
DummyPacketReceiver packetReceiver = new DummyPacketReceiver(receiveLatch);
IkeSocket.setPacketReceiver(packetReceiver);
// Send first packet.
sendToIkeSocket(mDummyRemoteServerFd, mDataOne, mLocalAddress);
receiveLatch.await();
assertEquals(1, ikeSocket.numPacketsReceived());
assertArrayEquals(mDataOne, packetReceiver.mReceivedData);
// Send second packet.
sendToIkeSocket(mDummyRemoteServerFd, mDataTwo, mLocalAddress);
receiveLatch.await();
assertEquals(2, ikeSocket.numPacketsReceived());
assertArrayEquals(mDataTwo, packetReceiver.mReceivedData);
// Close IkeSocket.
TestCountDownLatch closeLatch = new TestCountDownLatch();
ikeSocket
.getHandler()
.post(
() -> {
ikeSocket.releaseReference();
closeLatch.countDown();
});
closeLatch.await();
mIkeThread.quitSafely();
}
@Test
public void testHandlePacket() throws Exception {
byte[] recvBuf =
TestUtils.hexStringToByteArray(
NON_ESP_MARKER_HEX_STRING + IKE_REQ_MESSAGE_HEX_STRING);
mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap);
byte[] expectedIkePacketBytes = TestUtils.hexStringToByteArray(IKE_REQ_MESSAGE_HEX_STRING);
ArgumentCaptor<IkeHeader> ikeHeaderCaptor = ArgumentCaptor.forClass(IkeHeader.class);
verify(mMockIkeSessionStateMachine)
.receiveIkePacket(ikeHeaderCaptor.capture(), eq(expectedIkePacketBytes));
IkeHeader capturedIkeHeader = ikeHeaderCaptor.getValue();
assertEquals(mRemoteSpi, capturedIkeHeader.ikeInitiatorSpi);
assertEquals(mLocalSpi, capturedIkeHeader.ikeResponderSpi);
}
@Test
public void testHandleEspPacket() throws Exception {
byte[] recvBuf =
TestUtils.hexStringToByteArray(
NON_ESP_MARKER_HEX_STRING + IKE_REQ_MESSAGE_HEX_STRING);
// Modify Non-ESP Marker
recvBuf[0] = 1;
mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap);
verify(mMockIkeSessionStateMachine, never()).receiveIkePacket(any(), any());
}
@Test
public void testHandlePacketWithMalformedHeader() throws Exception {
String malformedIkePacketHexString = "5f54bf6d8b48e6e100000000000000002120220800000000";
byte[] recvBuf =
TestUtils.hexStringToByteArray(
NON_ESP_MARKER_HEX_STRING + malformedIkePacketHexString);
mPacketReceiver.handlePacket(recvBuf, mSpiToIkeStateMachineMap);
verify(mMockIkeSessionStateMachine, never()).receiveIkePacket(any(), any());
}
private byte[] receive(FileDescriptor mfd) throws Exception {
byte[] receiveBuffer = new byte[REMOTE_RECV_BUFF_SIZE];
AtomicInteger bytesRead = new AtomicInteger(-1);
Thread receiveThread =
new Thread(
() -> {
while (bytesRead.get() < 0) {
try {
bytesRead.set(
Os.recvfrom(
mDummyRemoteServerFd,
receiveBuffer,
0,
REMOTE_RECV_BUFF_SIZE,
0,
null));
} catch (Exception e) {
Log.e(
"IkeSocketTest",
"Error encountered reading from socket",
e);
}
}
Log.d(
"IkeSocketTest",
"Packet received with size of " + bytesRead.get());
});
receiveThread.start();
receiveThread.join(TIMEOUT);
return Arrays.copyOfRange(receiveBuffer, 0, bytesRead.get());
}
private void sendToIkeSocket(FileDescriptor fd, byte[] data, InetAddress destAddress)
throws Exception {
Os.sendto(fd, data, 0, data.length, 0, destAddress, mClientUdpEncapSocket.getPort());
}
private static class IkeSocketReceiver {
private IkeSocket mIkeSocket;
void setIkeSocket(IkeSocket ikeSocket) {
mIkeSocket = ikeSocket;
}
IkeSocket getIkeSocket() {
return mIkeSocket;
}
}
private static class DummyPacketReceiver implements IkeSocket.IPacketReceiver {
byte[] mReceivedData = null;
final TestCountDownLatch mLatch;
DummyPacketReceiver(TestCountDownLatch latch) {
mLatch = latch;
}
public void handlePacket(
byte[] revbuf, LongSparseArray<IkeSessionStateMachine> spiToIkeSession) {
mReceivedData = Arrays.copyOfRange(revbuf, 0, revbuf.length);
mLatch.countDown();
Log.d("IkeSocketTest", "Packet received");
}
}
private static class TestCountDownLatch {
private CountDownLatch mLatch;
TestCountDownLatch() {
reset();
}
private void reset() {
mLatch = new CountDownLatch(1);
}
void countDown() {
mLatch.countDown();
}
void await() {
try {
if (!mLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
fail("Time out");
}
} catch (InterruptedException e) {
fail(e.toString());
}
reset();
}
}
}